Note: The code in this repository, as well as this README, was generated iteratively with ChatGPT o1 and o1pro for my own needs. I have started making manual changes now, and PRs are welcome. Sharing because caring. -behdad
A Node.js script that exports Google Docs to HTML+CSS with high fidelity, preserving essential formatting like headings, line spacing, alignment, bullet lists, images, and more. Perfect for creating an offline or self-hosted version of your docs that closely matches the original.
Why this script? Because simpler exports often lose alignment, line spacing, or right-to-left details. This script pulls detailed styling info (like heading levels, inline font sizes, doc-based image sizes, and bullet indentation) directly from the Google Docs API. It then merges that styling into HTML and inserts a corresponding CSS that replicates Google Docs formatting while also neutralizing default browser quirks.
- Named Styles: Detects Title, Subtitle, HEADING_1..6, and applies them to HTML headings (H1..H6) or custom classes.
- Line Spacing & Margins: Honors
paragraphStyle.lineSpacing,spaceAbove,spaceBelow, indentation, alignment. - Right-to-Left: If the doc says a paragraph is RTL, we add
dir="rtl"and flip alignment (START → right). - Tables: GDocs tables become
<table>with<tr>and<td>, keeping paragraph formatting in each cell. - Images: Exports inline and positioned images at their document dimensions. JPEGs become high-quality WebP; opaque PNGs with photographic entropy also become WebP when that saves at least 20%. GIF, WebP, SVG, and other recognized formats keep their real extension, and transparent or graphic PNGs remain PNG.
- TOC: If your doc has a table of contents, we export it in a
<div class="doc-toc">, indenting each line by its heading level. - Bullet/Numbered Lists: Detects all GDocs list styles (disc, circle, square, dash bullets; decimal, roman, alphabetic numbering) with proper nesting. RTL lists use
<ul dir="rtl">so bullets align on the right. - Google Fonts: Gathers unique fonts used in the doc. Inserts a
<link>to fonts.googleapis.com so text families match. - Neutralized Headings: Browsers normally inflate
<h3>. We override heading tags (h1..h6 { font-size: 1em }) so Google Docs' inline style alone sets the final size. - Semantic Main Content: Wraps the exported document in
<main class="doc-content">so injected navigation can remain outside the page's primary-content landmark.
-
Prerequisites:
- Node.js (v20.9 or later).
- Run
npm installto install the declared dependencies. - A Google Cloud service account JSON file with read permissions on the doc.
-
Get the Script:
- Download or clone this repository.
- Put
service_account.jsonbesidegdocs-me-up.js, or setSERVICE_ACCOUNT_KEY_FILEto another path. The default works regardless of the directory from which the exporter is invoked.
-
Authenticate:
- Go to Google Cloud Console, enable Docs API + Drive API.
- Create a service account with suitable permissions.
- Download the JSON key file as
service_account.json. - Make sure your doc is accessible by that service account (share it if needed).
node gdocs-me-up.js <DOC_ID> <OUTPUT_DIR> [options]For help:
node gdocs-me-up.js --help<DOC_ID>: The unique ID from your doc's URL. For example:https://docs.google.com/document/d/1AbCdE-FgHiJKlMnOpQRs7TuVMue/edit ^^^^^^^^^^^^^^^^^^^^^<OUTPUT_DIR>: The folder where the script will writeindex.htmland animages/subfolder.--stylesheet <HREF>: Inserts an external stylesheet after the generated styles. Repeat the option to include multiple stylesheets in argument order. Hrefs are resolved relative to the generated HTML file.--script <SRC>: Inserts an external script immediately before</body>. Repeat the option to include multiple scripts in argument order. Sources are resolved relative to the generated HTML file.--html-name <NAME>: Replaces the defaultindex.htmlfilename.--images-dir <NAME>: Replaces the defaultimagesdirectory name.
Example:
node gdocs-me-up.js 1AbCdE-FgHiJK docs_exportTo apply stylesheets and scripts stored next to the output directory:
node gdocs-me-up.js 1AbCdE-FgHiJK docs_export \
--stylesheet ../style.css \
--stylesheet ../theme.css \
--script ../script.js \
--html-name article.html \
--images-dir assetsOn completion:
docs_export/index.html: Your doc in near-pixel HTML+CSS fidelity.docs_export/images/: Downloaded images.
Open docs_export/index.html in your browser. You'll see headings, bullet-lists, alignment, images, and more, closely mirroring the original doc.
The project includes comprehensive testing to ensure export quality:
npm testTests core utility functions (escapeHtml, color conversion, list detection, etc.) with 97% coverage.
npm test tests/content-verification.test.jsCompares Google Docs API data with exported HTML to verify:
- Text content accuracy
- Link preservation
- Image export
- Heading hierarchy
- List structures
Uses two stable reference documents as golden standards.
Full HTML regression detection - catches any change to output:
npm test # Runs automatically with other tests
npm test -- -u # Update snapshots after intentional changesnpm run test:visualGenerates side-by-side screenshots and analysis reports comparing Google Docs with exported HTML. Reports include:
- Structure analysis (element counts)
- Layout metrics (spacing, sizing)
- Actionable recommendations
See tests/visual/README.md for details.
To run the local visual-comparison corpus:
GDOCS_CORPUS_DIR=/path/to/fixtures npm run compare:corpusThe corpus runner deduplicates document IDs, captures the Google preview and local export at the same viewport, and writes screenshots, a contact sheet, and a JSON report under tests/visual/corpus/. Use -- --limit=5 for a short run or -- --names=story,butterflies to select fixture names. Fixture files and their location are not part of the repository.
-
Line Spacing: The script reads
paragraphStyle.lineSpacingand maps Google Docs' font-dependent metrics to browser line boxes. It also appliesspaceAbove+spaceBelowasmargin-top+margin-bottom. -
Right-to-Left Paragraphs: If
paragraphStyle.direction = RIGHT_TO_LEFT, we adddir="rtl". If alignment=START, it becomesright; alignment=END =>left. Lists also carrydir="rtl"so bullets go on the right side. -
Images: Supports both inline images and positioned objects (header photos, wrapped images). Images are constrained to container width while retaining their explicit aspect ratio. We read size info from both
imageProperties.sizeandembedded.size, converting points to pixels (~1.333 ratio) and respecting transforms. Positioned objects render at their anchor paragraph. JPEGs are encoded as quality-84 WebP. Opaque PNGs whose grayscale entropy indicates photographic content are tested against the same WebP encoding and converted only when the candidate is at least 20% smaller; graphic and transparent PNGs stay lossless. -
TOC Indentation: For each line in the doc’s table of contents, the script checks the heading level of the link target. It then adds a
<div class="toc-level-3">(for example) with a margin-left rule in the CSS. -
Compact Semantic Markup: Google Docs often splits text into many runs. Consecutive compatible runs are merged, common paragraph formatting is inherited, emphasis uses semantic tags such as
<strong>and<em>, and repeated declarations are deduplicated into generated CSS classes. Exported content does not repeat inlinestyleattributes. -
Heading Size: We reset browser heading defaults, then a generated class supplies the document's exact size and weight without the browser multiplying them.
-
Fonts: If your doc uses “Roboto” and “Lato,” we add a single
<link>tohttps://fonts.googleapis.com/css2?family=Roboto&family=Lato&display=swap, letting the final HTML use those fonts.
- Force a Different Column Width: Edit
computeDocContainerWidth()to remove the+ 50, or set a fixed width. - Line Spacing: If you want a global
line-height:1.2, remove or comment out the lines inrenderParagraphreferencingparagraphStyle.lineSpacing. - Heading Tags: If you’d rather not use
<h1>.. <h6>, replace them with<p class="doc-heading-level-X">in the code. Then style them in CSS as you like.
- Invalid Grant / 401: Check your service account JSON, or ensure the doc is shared with your service account email.
- Images All Full-Width: Possibly the doc’s stored size is as wide as the page. Shrink them in GDocs or scale them down.
- TOC Not Indented: Make sure your doc has headings labeled
HEADING_1..6. If your doc uses custom styles, the script may not see them as headings. - H3 Still Big: Confirm the code’s
<h3>CSS override is present, or remove any conflicting styles from your own stylesheet. - Using a Different Auth: If you want user-based OAuth, adapt
getAuthClient()to your flow.
Doc: “My Example Document” with:
- Heading 3 at 14pt
- Right-to-left paragraphs
- A table of contents
- Several images scaled to 50%
Command:
node gdocs-me-up.js 1XYZabc docs_exportResult:
docs_export/index.html: Headings, bullet-lists, alignment, images at half-size, lines spaced as in doc, etc.docs_export/images/: Images in their source format, with space-saving WebP conversion for JPEG and suitable opaque PNG photographs.- The TOC lines are indented by heading level.
Open the HTML in your browser or upload to a simple web server. Should be extremely close to the Google Doc’s layout, including RTL paragraphs and scaled images.
- Fork or clone this repository.
- Modify the script (e.g., add footnote support or custom style merges).
- Submit a Pull Request describing your changes, or open an issue with suggestions.
We welcome improvements or bug fixes. This script is licensed under MIT, so feel free to adapt or include it in your projects, with attribution appreciated.
Thanks for checking out GDocs-Me-Up! We hope it helps you create accurate offline or self-hosted versions of your docs. If you have suggestions, issues, or ideas, please open an issue or PR. Happy exporting!