Export any Google Docs page — including view-only documents — to a properly formatted
.docxfile directly from your browser.
Author: Quang Huy Tran · quanghuytran.hust@gmail.com
.docxactiveTab + scripting, no browsing history access| Tool | Version |
|---|---|
| Node.js | ≥ 18 |
| npm | ≥ 9 (or yarn) |
| Chrome / Edge | ≥ 88 (Manifest V3) |
npm install
npm run icons
This runs scripts/generate-icons.js and writes four PNG files to src/icons/:
icon16.png, icon32.png, icon48.png, icon128.png.
npm run build
The compiled extension is written to the dist/ directory:
dist/
├── icons/
│ ├── icon16.png
│ ├── icon32.png
│ ├── icon48.png
│ └── icon128.png
├── manifest.json
├── popup.html
└── popup.js ← ~330 KB bundled (includes docx library)
chrome://extensions/dist/ foldernpm run dev
Webpack rebuilds dist/popup.js whenever you edit any file in src/.
After each rebuild, click the ↺ refresh button on chrome://extensions/ to reload the extension.
npm run clean && npm run build
docs-scanner/
│
├── src/ Source files (TypeScript + HTML)
│ ├── manifest.json Chrome Extension manifest (MV3)
│ ├── popup.html Extension popup UI
│ ├── popup.ts Popup orchestrator — handles all user interactions
│ ├── extractor.ts HTML parser — converts mobilebasic HTML → document tree
│ └── docxBuilder.ts DOCX generator — converts document tree → .docx Blob
│
├── scripts/
│ └── generate-icons.js Generates PNG icons from SVG via sharp
│
├── dist/ Build output (load this folder into Chrome)
│ └── ...
│
├── package.json
├── tsconfig.json
└── webpack.config.js
[User clicks "Preview" or "Export .docx"]
│
▼
chrome.tabs.query() Get active tab ID (no URL — avoids 'tabs' permission)
│
▼
scripting.executeScript() Inject into Google Docs tab:
└─ window.location.href 1. Read current URL
└─ fetch(/mobilebasic) 2. Fetch simplified HTML using session cookies
└─ fetch(image URLs) 3. Fetch each image as base64
│
▼
extractor.ts Parse mobilebasic HTML:
└─ parseStyleSheets() Extract CSS class rules (.c0, .c1 → bold/italic/…)
└─ Walk DOM h1–h6, p, ul/ol/li, table, img
└─ extractRuns() Text runs with formatting (bold, italic, color, link)
└─ extractCellParagraphs() Multi-paragraph table cells
│
▼
docxBuilder.ts Build .docx:
└─ buildTextRuns() TextRun / ExternalHyperlink objects
└─ buildTable() Table with borders + header shading
└─ buildImageParagraph() ImageRun with auto-scaled dimensions
└─ Packer.toBlob() Final Blob
│
▼
downloadBlob() Trigger browser download → filename.docx
Key insight — why /mobilebasic?
Google Docs’ editor uses a canvas-like renderer (the “kix” engine) which is nearly impossible to scrape reliably. The /mobilebasic endpoint returns a lightweight, semantic HTML version of the same document — using real <h1>, <p>, <table>, <ul> tags — making parsing straightforward. Because the fetch happens inside the tab’s context, it carries the user’s existing Google session cookie, so it works for documents the user has access to (including view-only).
| Command | Description |
|---|---|
npm run build |
Production build → dist/ |
npm run dev |
Watch mode (rebuilds on change) |
npm run icons |
Regenerate PNG icons from SVG templates |
npm run clean |
Delete dist/ directory |
| Permission | Why it’s needed |
|---|---|
activeTab |
Grants temporary access to the currently active tab when the user clicks the extension icon |
scripting |
Injects the fetch function into the Google Docs tab to retrieve the document HTML and images using the user’s session |
host_permissions: docs.google.com |
Restricts script injection to Google Docs pages only |
No
tabspermission — the extension never reads the URL list of your open tabs. The current page URL is obtained by injecting a one-line script (window.location.href) into the active tab only.
npm run builddist/ directory (not the project root):
cd dist && zip -r ../docport-v1.0.0.zip . && cd ..
docport-v1.0.0.zipMIT © Quang Huy Tran