English | 简体中文
marktodocx converts Markdown to Word (.docx) while preserving headings, paragraphs, lists, tables, code blocks, blockquotes, local images, and Mermaid diagrams. The same conversion rules are shared across every supported host so that one fix lands everywhere instead of being copied between tools.
marktodocx targets three public registries — the VS Code Marketplace, the Chrome Web Store, and ClawHub (the OpenClaw skill registry) — plus GitHub Releases for skill zip mirrors. The VS Code extension and agent skill are already live on their public registries, while the Chrome extension is still pending Chrome Web Store publication. The full publish path is wired up in docs/publishing.md. The CLI is intentionally source-only and stays that way.
Mermaid support differs by host:
- Chrome extension and VSCode extension include Mermaid through the browser runtime after you build the host.
- CLI and agent skill run on the Node runtime by default, so Mermaid requires Node-side Chromium support through
@marktodocx/runtime-node-mermaidor a Mermaid-enabled agent-skill export.
Markdown source preview:
Generated DOCX preview:
- Preview
- Quickstart
- Host Status
- Architecture
- Package Layout
- Supported Features
- Requirements
- Installation
- CLI Usage
- CLI Style Options
- Chrome Extension
- VSCode Extension
- Agent Skill
- Development
- Limitations
- Troubleshooting
- Help
npm install- If your Markdown contains Mermaid, run
npx puppeteer browsers install chrome - Convert a file with
node md-to-docx.mjs report.md - The output lands next to
report.mdunless you pass an explicit output path
npm install && npm run build:chrome-extension- Open
chrome://extensions/, enable Developer mode, and click Load unpacked - Select
apps/chrome-extension/dist/ - Pin the marktodocx icon, open it, select the folder containing your Markdown file and local images, choose the
.mdfile, then click Convert - The generated
.docxdownloads through Chrome's normal download flow: if Ask where to save each file is enabled, Chrome opens a save dialog; otherwise it uses the default download directory
npm install && npm run package:vscode-extension- Install
apps/vscode-extension/dist/marktodocx-vscode-extension.vsix(for example:code --install-extension apps/vscode-extension/dist/marktodocx-vscode-extension.vsix --force) - Open the workspace containing your Markdown file and local images
- Right-click a
.mdfile in the Explorer and choose marktodocx: Convert to DOCX, or runmarktodocx.convertToDocxfrom the Command Palette - Choose the output path in the save dialog
npm install && npm run export:agent-skill- Copy
apps/agent-skill/dist/marktodocx-skill/into your skill host, for example~/.claude/skills/marktodocx-skillfor Claude Code or your OpenClaw skills directory - Start a new agent session and invoke the skill explicitly, for example:
Convert docs/report.md to DOCX with stylePreset=minimal. - If your Markdown contains Mermaid, use
npm run export:agent-skill:mermaidinstead (platform-specific; export on the same OS and architecture you will deploy to)
| Host | Status | Entry Point | Release Target |
|---|---|---|---|
| Chrome extension | Implemented | apps/chrome-extension/ |
Chrome Web Store |
| VSCode extension | Implemented | apps/vscode-extension/ |
VS Code Marketplace |
| Agent skill | Implemented | apps/agent-skill/ |
ClawHub + GitHub Releases |
| CLI | Implemented | md-to-docx.mjs |
Source-only (no registry) |
Release process for every host above is documented in docs/publishing.md.
The repository follows a Shared Core + Two Runtime Families layout:
- A shared conversion core owns the canonical Markdown → HTML → DOCX rules, the style and layout schemas, DOCX normalization, and the parity fixtures.
- A browser runtime family (
@marktodocx/runtime-browser) hosts the Chrome extension and VSCode extension on top of nativeDOMParserand in-page Mermaid rendering. - A Node runtime family (
@marktodocx/runtime-node, plus the optional@marktodocx/runtime-node-mermaid) hosts the CLI and agent skill on top of a jsdom DOM adapter and an optional Puppeteer-based Mermaid renderer.
Architecture diagram:
Output parity across hosts is enforced by fixture-driven gates in scripts/run-fixture-parity.mjs, scripts/run-cli-parity.mjs, scripts/run-vscode-parity.mjs, and scripts/run-agent-skill-parity.mjs. See docs/design-core-refactor.md for the full design, contracts, and rationale.
Repository cleanup is still intentionally incremental in one place only: md-to-docx.mjs remains the public CLI entry point until the final dead-code removal step is backed by stable parity history.
marktodocx/
├── md-to-docx.mjs # Thin CLI wrapper (argument parsing + style resolution)
├── packages/
│ ├── core/ # @marktodocx/core — canonical rules, schemas, fixtures
│ ├── runtime-browser/ # @marktodocx/runtime-browser — native DOMParser + in-page Mermaid
│ ├── runtime-node/ # @marktodocx/runtime-node — jsdom adapter + filesystem image map
│ └── runtime-node-mermaid/ # @marktodocx/runtime-node-mermaid — optional Puppeteer Mermaid renderer
├── apps/
│ ├── agent-skill/ # Agent skill host (Node runtime family)
│ ├── chrome-extension/ # Chrome extension host
│ └── vscode-extension/ # VSCode extension host (hidden webview + browser runtime)
├── test-markdown/__golden__/ # Parity fixtures and golden DOCX artifacts
└── docs/design-core-refactor.md # Authoritative design document
- Headings, paragraphs, bold, italic, blockquotes
- Ordered and unordered lists
- Code blocks (with syntax highlighting) and inline code
- Markdown tables
- Local images (
.png,.jpg,.jpeg,.gif,.webp,.svg) - Mermaid diagrams (rendered to PNG and embedded in the DOCX)
- Node.js 22+
- npm (workspaces enabled)
- Chrome extension: Chrome or Chromium 116+ with support for loading unpacked MV3 extensions
- VSCode extension: VS Code 1.97+
- CLI: Node.js 22+
- Agent skill: Node.js 22+ plus a compatible skill host such as Claude Code or OpenClaw
- Mermaid on Node hosts: a Puppeteer-managed Chrome or Chromium binary, plus host Linux shared libraries when applicable
End users can already install the published hosts from their public registries — VS Code Marketplace for the VSCode extension and ClawHub (clawhub install marktodocx-skill) for the agent skill. The Chrome extension is still waiting on Chrome Web Store publication, and the CLI is intentionally source-only.
The instructions below cover the source build, which works for every host and is required for the CLI:
npm installThis installs all workspace packages, including @marktodocx/core, @marktodocx/runtime-browser, @marktodocx/runtime-node, and @marktodocx/runtime-node-mermaid.
If your Markdown contains Mermaid diagrams and you plan to convert it via the CLI, install a Puppeteer-managed Chrome once:
npx puppeteer browsers install chromeBasic invocation:
node md-to-docx.mjs <input.md> [output.docx] [options]If the output path is omitted, the CLI writes a .docx next to the input Markdown:
node md-to-docx.mjs report.md
# -> report.docxExplicit output path:
node md-to-docx.mjs report.md dist/report.docxYou can also use the npm script shortcut:
npm run convert -- report.md dist/report.docxThe CLI resolves shared styleOptions through @marktodocx/runtime-node. All configuration goes through the same schema used by every host, so a preset set on the CLI produces the same output as the same preset configured in the extension.
| CLI flag | Environment variable | Purpose |
|---|---|---|
--style-preset <name> |
MARKTODOCX_STYLE_PRESET |
Base style preset (default, minimal, report) |
--margin-preset <name> |
MARKTODOCX_MARGIN_PRESET |
Page margin preset (default, compact, wide) |
--style-json <json|path> |
MARKTODOCX_STYLE_JSON |
Inline JSON string or path to a JSON file |
--set key=value |
MARKTODOCX_STYLE_SET |
Targeted dotted-path override (repeatable) |
Resolution precedence (later entries override earlier ones): environment preset → environment JSON → environment margin → environment assignments → CLI preset → CLI JSON → CLI margin → CLI assignments. The resolved object is then validated by core's normalizeStyleOptions.
--set and MARKTODOCX_STYLE_SET share the same dotted-path, semicolon-separated syntax:
code.fontSizePt=11;blockquote.italic=false;page.marginPreset=wide
Every --set assignment writes into overrides.<path>. These are the supported dotted paths:
body.fontFamilybody.fontSizePtbody.lineHeightbody.colorheadings.fontFamilyheadings.colortables.borderColortables.headerBackgroundColortables.headerTextColorcode.fontFamilycode.fontSizePtcode.syntaxThemecode.inlineBackgroundColorcode.inlineItaliccode.blockBackgroundColorcode.blockBorderColorcode.languageBadgeColorblockquote.backgroundColorblockquote.textColorblockquote.borderColorblockquote.italicpage.marginPreset
Value parsing rules:
trueandfalsebecome booleans- numeric values such as
11or1.55become numbers - everything else is treated as a trimmed string
Example:
node md-to-docx.mjs report.md \
--set body.fontSizePt=12 \
--set body.lineHeight=1.6 \
--set code.syntaxTheme=dark \
--set blockquote.italic=false--style-json and MARKTODOCX_STYLE_JSON accept either:
- an inline JSON object string
- a path to a JSON file
The JSON can use either full styleOptions shape:
{
"preset": "minimal",
"overrides": {
"body": {
"fontSizePt": 12
}
}
}or the shorthand object form that contains only override groups:
{
"body": {
"fontSizePt": 12
},
"page": {
"marginPreset": "wide"
}
}See the full example file at docs/style-options.example.json.
Example combining several options:
node md-to-docx.mjs report.md dist/report.docx \
--style-preset minimal \
--margin-preset wide \
--style-json ./docs/style-options.example.json \
--set body.fontSizePt=12 \
--set blockquote.italic=falseThe Chrome extension lives under apps/chrome-extension/ and shares conversion logic with the CLI through @marktodocx/core + @marktodocx/runtime-browser.
Build it from source:
npm install
npm run build:chrome-extension- Open
chrome://extensions/. - Enable Developer mode.
- Click Load unpacked.
- Select
apps/chrome-extension/dist/.
- Pin the marktodocx icon if you want quick access from the toolbar.
- Click the extension icon to open the conversion page.
- Select the folder that contains the Markdown file and any local images it references.
- Choose the target Markdown file from the file selector.
- Adjust style options if needed, then click Convert.
- Chrome starts a normal browser download for the generated
.docx. If Chrome is configured to ask where to save each file, you will get a save dialog; otherwise it goes to the default download directory.
The extension resolves local image paths relative to the Markdown file. A single-file picker would not grant access to sibling or parent directories referenced by Markdown image links, so the extension requires the containing folder instead.
The VSCode extension lives under apps/vscode-extension/. It activates the marktodocx.convertToDocx command from the explorer, editor, and editor-title context menus, and routes conversion through a hidden webview that bundles @marktodocx/runtime-browser.
Build the bundle with:
npm run build:vscode-extensionFor an installable artifact, package it as a .vsix:
npm run package:vscode-extensionThen install apps/vscode-extension/dist/marktodocx-vscode-extension.vsix into VS Code. If you are developing the extension instead, point VS Code at apps/vscode-extension/ through the Run Extension launch configuration.
- Right-click a
.mdfile in the Explorer and choose marktodocx: Convert to DOCX. - Or open the Command Palette and run
marktodocx.convertToDocx. - Or use the editor context menu or editor title button when a Markdown file is open.
VS Code prompts for the output path with a save dialog. The .docx is written wherever you choose, and local images resolve relative to the workspace folder containing the Markdown file.
Conversion settings live under the marktodocx namespace and map directly onto the shared styleOptions schema:
| Setting | Purpose |
|---|---|
marktodocx.stylePreset |
Base style preset (default, minimal, report) |
marktodocx.marginPreset |
Optional page margin preset override (default, compact, wide) |
marktodocx.styleJson |
Inline style JSON string or workspace-relative path to a style JSON file |
marktodocx.styleSet |
Targeted dotted-path overrides such as body.fontSizePt=12 (array of strings) |
Local images are resolved against the workspace folder of the converted Markdown file, mirroring the Chrome extension's directory-selection contract.
The extension is packaged with @vscode/vsce. The package script builds the bundle, runs the bundle smoke check, and writes a standalone .vsix to apps/vscode-extension/dist/marktodocx-vscode-extension.vsix.
Run any of these from the repository root:
npm run package:vscode-extension
# or
npm run package -w marktodocx-vscode-extensionOr from the extension directory:
cd apps/vscode-extension
npm run packageInstall the produced .vsix into your local VS Code:
npm run install:vsix -w marktodocx-vscode-extension
# or
code --install-extension apps/vscode-extension/dist/marktodocx-vscode-extension.vsix --forceThe packaged .vsix ships the bundled dist/extension.cjs, the webview asset bundle, and the manifest. Workspace @marktodocx/* packages are inlined at build time, so the extension does not require node_modules at install time and vsce package is invoked with --no-dependencies.
The agent skill source lives under apps/agent-skill/ and exports convertWithAgentSkill() from apps/agent-skill/skill.mjs. It is intentionally thin: it resolves file or inline Markdown input, maps skill parameters and environment defaults into the shared styleOptions schema through @marktodocx/runtime-node, and writes a DOCX when an output path is available.
If you want a host-specific deployment walkthrough, see apps/agent-skill/README.md for Claude Code and OpenClaw recipes.
Naming rule:
- Internal source app path:
apps/agent-skill/ - Internal workspace package:
marktodocx-agent-skill - Public Claude skill name:
marktodocx-skill
The source folder name does not need to match the public Claude skill name. The public skill identity is defined by the name field in apps/agent-skill/SKILL.md.
Skill parameters match the design contract:
| Parameter | Purpose |
|---|---|
inputPath |
Path to a local Markdown file |
markdown |
Inline Markdown string |
baseDir |
Base directory for resolving local images with inline Markdown |
outputPath |
Optional DOCX path; defaults beside inputPath when file input is used |
stylePreset |
Base shared style preset |
marginPreset |
Optional shared margin preset override |
styleJson |
Shared style JSON string, plain object, or JSON file path |
styleSet |
Shared dotted-path overrides such as body.fontSizePt=12 |
The skill honors the same environment defaults as the CLI: MARKTODOCX_STYLE_PRESET, MARKTODOCX_MARGIN_PRESET, MARKTODOCX_STYLE_JSON, and MARKTODOCX_STYLE_SET. Mermaid behavior is also the same: install @marktodocx/runtime-node-mermaid when the document contains Mermaid blocks.
To produce a standalone deployable skill folder that does not depend on a live repository checkout, run:
npm run export:agent-skillThis writes apps/agent-skill/dist/marktodocx-skill/ plus apps/agent-skill/dist/marktodocx-skill.zip, which can be copied or symlinked into another agent runtime's skills directory.
To produce a Mermaid-enabled export with a vendored Chromium browser, run:
npm run export:agent-skill:mermaidThat profile is platform-specific and intended for deployment on the same OS and CPU family that built the export. It also persists the working Chromium launch args in the export manifest, so sandbox-restricted hosts that built the export usually do not need manual environment flags at runtime. For Mermaid diagrams with Simplified Chinese labels, the Node renderer also injects a bundled Noto Sans SC webfont into the SVG before Chromium rasterizes it, so the export does not rely on host CJK fonts.
For a CI-safe export gate, run:
npm run test:export:agent-skillThat command rebuilds the export and verifies the final artifact layout.
For the Mermaid-enabled export gate, run:
npm run test:export:agent-skill:mermaidPer-package build scripts:
npm run build:core
npm run build:runtime-browser
npm run build:runtime-node
npm run build:runtime-node-mermaid
npm run build:packages
npm run build:agent-skill
npm run export:agent-skill
npm run export:agent-skill:mermaid
npm run test:export:agent-skill
npm run test:export:agent-skill:mermaid
npm run build:chrome-extension
npm run build:vscode-extension
npm run build:cli
npm run smoke:allUnit and parity gates:
npm run test:unit # Unit tests across packages
npm run test:parity # Extension-path parity gate
npm run test:parity:cli # CLI-path parity gate
npm run test:parity:skill # Agent-skill parity gate
npm run test:parity:vscode # VSCode-path parity gate
npm run test:parity:all # Full pre-ship parity gate (extension + CLI + VSCode + agent skill)The full parity gate is npm run test:parity:all. Run it before shipping any change that touches the conversion core or any runtime family.
GitHub Actions now mirrors the same local flow in .github/workflows/ci.yml: one job installs dependencies and runs npm run smoke:all (which builds the shared packages, smoke-tests every host, and verifies the standalone agent-skill export); a second job installs Puppeteer-managed Chrome and runs npm run test:parity:all on every pull request.
To refresh fixtures when canonical output intentionally changes:
npm run generate:goldensParity maintenance rule: npm run test:parity validates every status: "verified" fixture listed in test-markdown/__golden__/manifest.json, not every Markdown file under test-markdown/.
If you edit a manifest-listed fixture input, refresh the matching fixture id before rerunning parity:
npm run generate:goldens -- --refresh <fixture-id>
npm run generate:goldens -- --refresh allThis applies to both the fixture Markdown file recorded in markdownPath and any local assets referenced by that fixture. Editing ad hoc Markdown files that are not listed in the manifest does not affect npm run test:parity.
- Mermaid diagrams are embedded as PNG images, not as editable Mermaid sources inside Word.
- Very large Mermaid diagrams may still be scaled by Word to fit the page width.
- Markdown → DOCX fidelity is best-effort because HTML/CSS and Word's rendering model do not match exactly.
- Remote images are not downloaded. Use local image files for reliable embedding.
- The Chrome extension needs directory selection (not a single file) so it can resolve relative image paths.
If the CLI logs a Could not find Chrome error during Mermaid rendering:
npx puppeteer browsers install chromeIf Mermaid rendering fails with an error like error while loading shared libraries: libatk-1.0.so.0, Chromium was downloaded successfully but the host OS is missing Puppeteer's Linux runtime libraries.
On Debian or Ubuntu, if you have root access, let Puppeteer attempt to install them:
sudo MARKTODOCX_PUPPETEER_INSTALL_DEPS=1 npm run test:export:agent-skill:mermaidIf you prefer to install packages manually on Debian or Ubuntu, start with:
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
fonts-liberation \
libasound2t64 || sudo apt-get install -y libasound2
sudo apt-get install -y \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdbus-1-3 \
libdrm2 \
libgbm1 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxkbcommon0 \
libxrandr2 \
xdg-utilsOn some Ubuntu releases the audio package is named libasound2t64; on older releases it is still libasound2.
In containerized environments you may also need MARKTODOCX_PUPPETEER_NO_SANDBOX=1.
If Chromium fails with No usable sandbox!, rerun the Mermaid export gate as:
MARKTODOCX_PUPPETEER_NO_SANDBOX=1 npm run test:export:agent-skill:mermaidOn minimal Ubuntu VPS hosts, you may need both environment variables together:
sudo MARKTODOCX_PUPPETEER_INSTALL_DEPS=1 MARKTODOCX_PUPPETEER_NO_SANDBOX=1 npm run test:export:agent-skill:mermaidIf the CLI exits with a message about installing @marktodocx/runtime-node-mermaid, your Markdown contains a Mermaid fence but the optional Puppeteer helper package is not installed. Install the workspace (which pulls it in) and rerun:
npm installThe shared pipeline accepts these extensions:
.png.jpg.jpeg.gif.webp.svg
Convert any other format to one of the above before running the CLI.
Mermaid layout parameters are now owned by the shared runtime configuration, not by the CLI wrapper. Adjust them in the shared Mermaid config rather than reintroducing CLI-local drift.
Print the CLI usage reference:
node md-to-docx.mjs --help

