@mohtasham/md-to-docx
v2.10.2
Published
Convert Markdown to Microsoft Word (.docx) documents with support for various Markdown features
Maintainers
Readme
@mohtasham/md-to-docx
Convert Markdown to Microsoft Word (
.docx) documents — in Node.js, in the browser, or straight from your terminal.
npm version npm downloads license types node
A TypeScript-first library and CLI that turns Markdown into production-ready Word documents: headings, tables, lists, images, code blocks with optional syntax highlighting, multi-section templates, per-section headers/footers, page numbering, TOC, and fine-grained style control.
Table of contents
- Highlights
- Installation
- Quick start
- CLI
- Programmatic usage
- Features
- Supported Markdown
- API reference
- Requirements
- Install as an agent skill
- Development
- Changelog
- Contributing
- License
Highlights
- Zero-config defaults — pass any Markdown string, get a valid
.docxBlob back. - First-class CLI —
npx @mohtasham/md-to-docx input.md output.docx. - TypeScript-native — fully typed options surface, including
CodeHighlightTheme,Options, andDocumentSection. - Multi-section documents — cover pages, per-section headers/footers, page numbering resets, mixed orientations, style overrides.
- Optional syntax highlighting — opt-in, powered by
[lowlight](https://github.com/wooorm/lowlight); ships a GitHub-light theme and lets you override any token color. - Works everywhere — Node.js (18+) and modern browsers; the package ships ESM with type declarations.
- Small public surface, stable API — only the root entrypoint is exported via
package.json#exports.
Installation
npm install @mohtasham/md-to-docx
# or
pnpm add @mohtasham/md-to-docx
# or
yarn add @mohtasham/md-to-docxQuick start
import { convertMarkdownToDocx } from "@mohtasham/md-to-docx";
import fs from "node:fs/promises";
const markdown = `
# Hello, Word
This document was generated from **Markdown** in TypeScript.
- Supports lists
- **Bold**, *italic*, ++underline++, ~~strikethrough~~
- Tables, blockquotes, images, and code blocks
\`\`\`ts
const greet = (name: string) => \`Hello, \${name}!\`;
\`\`\`
`;
const blob = await convertMarkdownToDocx(markdown);
await fs.writeFile("hello.docx", Buffer.from(await blob.arrayBuffer()));CLI
Convert files without writing any code:
# Run without installing
npx @mohtasham/md-to-docx input.md output.docx
# Or install globally
npm install -g @mohtasham/md-to-docx
md-to-docx input.md output.docx
# Apply styling or multi-section config from a JSON file
md-to-docx input.md output.docx --options options.json
md-to-docx input.md output.docx -o options.json
# Help
md-to-docx --helpThe --options JSON file accepts the same shape as the programmatic Options argument. Example:
{
"documentType": "report",
"style": {
"fontFamily": "Trebuchet MS",
"heading1Alignment": "CENTER",
"paragraphAlignment": "JUSTIFIED"
},
"codeHighlighting": { "enabled": true }
}For a multi-section document, use template + sections (the CLI ignores the positional markdown argument when sections is provided):
{
"template": {
"pageNumbering": { "display": "current", "alignment": "CENTER" }
},
"sections": [
{
"markdown": "# Cover\n\nPrepared for ACME",
"footers": { "default": null },
"pageNumbering": { "display": "none" }
},
{
"markdown": "[TOC]\n\n# Body\n\nMain content…",
"headers": { "default": { "text": "Main Section", "alignment": "RIGHT" } },
"pageNumbering": { "start": 1, "formatType": "decimal" }
}
]
}Programmatic usage
Browser
import { convertMarkdownToDocx, downloadDocx } from "@mohtasham/md-to-docx";
const blob = await convertMarkdownToDocx("# Hello\n\nWorld.");
downloadDocx(blob, "hello.docx");Node.js
import { convertMarkdownToDocx } from "@mohtasham/md-to-docx";
import fs from "node:fs/promises";
const blob = await convertMarkdownToDocx(await fs.readFile("input.md", "utf8"));
await fs.writeFile("output.docx", Buffer.from(await blob.arrayBuffer()));React
import { useState } from "react";
import { convertMarkdownToDocx, downloadDocx } from "@mohtasham/md-to-docx";
export function MarkdownExporter() {
const [markdown, setMarkdown] = useState("");
const exportDocx = async () => {
const blob = await convertMarkdownToDocx(markdown);
downloadDocx(blob, "export.docx");
};
return (
<>
<textarea value={markdown} onChange={(e) => setMarkdown(e.target.value)} />
<button onClick={exportDocx}>Export as DOCX</button>
</>
);
}Features
Multi-section documents (template + sections)
Use template for shared section defaults and sections for explicit parts with their own markdown, headers, footers, page numbering, orientation, and style overrides.
const blob = await convertMarkdownToDocx("", {
style: { fontFamily: "Trebuchet MS", paragraphSize: 24 },
template: {
page: {
margin: { top: 1440, right: 1080, bottom: 1440, left: 1080 },
},
pageNumbering: { display: "current", alignment: "CENTER" },
},
sections: [
{
markdown: "# My Report\n\nPrepared for ACME",
footers: { default: null },
pageNumbering: { display: "none" },
style: { paragraphAlignment: "CENTER", paragraphSize: 28 },
},
{
markdown: "[TOC]\n\n# Executive Summary\n\n…",
titlePage: true,
type: "NEXT_PAGE",
headers: {
default: { text: "Executive Summary", alignment: "RIGHT" },
first: { text: "Executive Summary (First)", alignment: "RIGHT" },
},
footers: {
default: {
text: "Page",
pageNumberDisplay: "currentAndSectionTotal",
alignment: "RIGHT",
},
},
pageNumbering: { start: 1, formatType: "decimal" },
style: { paragraphAlignment: "JUSTIFIED" },
},
{
markdown: "# Appendix\n\n…",
type: "ODD_PAGE",
page: { size: { orientation: "LANDSCAPE" } },
pageNumbering: { start: 1, formatType: "upperRoman" },
style: { paragraphSize: 22 },
},
],
});Precedence (last wins): global style → template.style → per-section style. For headers / footers, each slot (default, first, even) can inherit, override, or be explicitly disabled with null.
Syntax-highlighted code blocks
Highlighting is opt-in; when disabled (the default) output is byte-identical to pre-highlighting versions. When enabled, each token becomes its own colored TextRun.
await convertMarkdownToDocx(markdown, {
codeHighlighting: {
enabled: true,
showLanguageLabel: true,
languages: ["typescript", "javascript", "python", "bash"],
theme: {
background: "0D1117",
border: "30363D",
default: "C9D1D9",
languageLabel: "8B949E",
keyword: "FF7B72",
string: "A5D6FF",
number: "79C0FF",
comment: "8B949E",
"title.function": "D2A8FF",
},
},
});Theme keys map 1:1 to hljs-* token classes (without the hljs- prefix); values are RRGGBB hex strings without #. Reserved keys: default, background, border, languageLabel. Unknown or non-whitelisted languages fall back to the plain rendering path, so conversion never throws on an unsupported fence.
Custom heading and paragraph alignment
Each heading level and block type can be aligned independently:
await convertMarkdownToDocx(markdown, {
style: {
heading1Alignment: "CENTER",
heading2Alignment: "RIGHT",
heading3Alignment: "JUSTIFIED",
paragraphAlignment: "JUSTIFIED",
blockquoteAlignment: "CENTER",
},
});Set headingAlignment to provide a fallback for any level without its own override.
Table of Contents styling
Drop [TOC] on its own line in your markdown to render a clickable, auto-populated table of contents. Every TOC level is individually styleable:
await convertMarkdownToDocx(markdown, {
style: {
tocFontSize: 22,
tocHeading1FontSize: 28, tocHeading1Bold: true,
tocHeading2FontSize: 24, tocHeading2Bold: true,
tocHeading3FontSize: 22,
tocHeading4FontSize: 20, tocHeading4Italic: true,
tocHeading5FontSize: 18, tocHeading5Italic: true,
},
});Text find-and-replace
Run string, regex, or functional replacements over the Markdown AST before conversion — they apply across every element type (headings, paragraphs, list items, table cells, etc.):
await convertMarkdownToDocx(markdown, {
textReplacements: [
{ find: /oldText/g, replace: "newText" },
{ find: "Company Name", replace: "Acme Corp" },
{ find: /(\d+)/g, replace: (match) => `Number: ${match}` },
],
});RTL / bidirectional text
await convertMarkdownToDocx(markdown, {
style: {
direction: "RTL",
paragraphAlignment: "RIGHT",
},
});Supported Markdown
| Feature | Syntax | Notes |
| ----------------- | ---------------------- | ---------------------------------------------------- |
| Headings | # … ##### | H1–H5, individually styleable |
| Bold / italic | **bold**, *italic* | |
| Underline | ++underline++ | Custom marker |
| Strikethrough | ~~text~~ | GFM |
| Inline code | code | |
| Code blocks | ````` fenced | Optional syntax highlighting per block |
| Lists | -, *, 1. | Bullet, numbered, nested, rich formatting inside |
| Tables | | a |
| Blockquotes |> text | |
| Links |text | |
| Images | | HTTP(S) anddata:URLs; supports#w=…&h=…sizing |
| Horizontal rule |--- | Skipped during conversion |
| Table of Contents |[TOC] | Clickable, auto-populated |
| Page break |\pagebreak | Place on its own line |
| Comments |COMMENT: text` | Rendered as Word comments |
API reference
convertMarkdownToDocx(markdown, options?): Promise<Blob>
Converts Markdown text (or a multi-section template) to a DOCX Blob.
| Argument | Type | Description |
| ---------- | ---------- | ----------------------------------------------------------- |
| markdown | string | Markdown source. Ignored if options.sections is provided. |
| options | Options? | See below. |
downloadDocx(blob, filename?): void
Browser-only helper that triggers a file download. Throws in non-browser environments. filename defaults to "document.docx".
Options
interface Options {
documentType?: "document" | "report";
style?: Style;
template?: DocumentSection;
sections?: DocumentSection[];
codeHighlighting?: CodeHighlightOptions;
textReplacements?: TextReplacement[];
}Style
Text sizes
| Option | Type | Description |
| --------------------------------------------- | --------------------- | ---------------------------------------- |
| fontFamily | string | Base font family |
| fontFamilly | string (deprecated) | Alias for fontFamily |
| titleSize | number | Title size (half-points) |
| heading1Size … heading5Size | number | Per-level heading sizes |
| paragraphSize | number | Paragraph size |
| listItemSize | number | List item size |
| codeBlockSize | number | Code block size |
| blockquoteSize | number | Blockquote size |
| tocFontSize | number | TOC entry size (fallback for all levels) |
| tocHeading1FontSize … tocHeading5FontSize | number | Per-level TOC entry size |
| tocHeading1Bold … tocHeading5Bold | boolean | Per-level TOC entry bold flag |
| tocHeading1Italic … tocHeading5Italic | boolean | Per-level TOC entry italic flag |
Spacing & layout
| Option | Type | Description |
| ------------------ | ---------- | ------------------------------------- |
| headingSpacing | number | Space before/after headings |
| paragraphSpacing | number | Space before/after paragraphs |
| lineSpacing | number | Line spacing multiplier (e.g. 1.15) |
| tableLayout | "autofit" | "fixed" |
Alignment & direction
| Option | Type | Description |
| ----------------------------------------- | ------- | ------------------- |
| paragraphAlignment | "LEFT" | "RIGHT" |
| blockquoteAlignment |"LEFT" | "RIGHT" |
| headingAlignment | "LEFT" | "RIGHT" |
| heading1Alignment…heading5Alignment| Same | Overrides per level |
|direction |"LTR" | "RTL"` |
DocumentSection (for template and each entry in sections)
| Field | Type | Description |
| --------------- | ----------------------------- | -------------------------------------------------------------------------------------------- |
| markdown | string | Section-local markdown (required on entries in sections). |
| style | Style | Section-local style overrides. |
| headers | { default?, first?, even? } | Header slots. Each can be null to disable, or { text?, alignment?, pageNumberDisplay? }. |
| footers | Same as headers | Footer slots. |
| pageNumbering | See below | Section-local numbering and reset behavior. |
| page | { margin?, size? } | Section page geometry (margins, size, orientation: "PORTRAIT" |
| titlePage |boolean | Enables first-page header/footer behavior. |
|type |"NEXT_PAGE" | "CONTINUOUS" |
**pageNumbering:**
| Field | Type |
| ------------ | -------------- |
| display | "none" |
| alignment |"LEFT" |
| start | number (≥ 1) |
| formatType | "decimal" |
| separator |"hyphen" |
CodeHighlightOptions
| Option | Type | Default | Description |
| ------------------- | ----------------------------- | ------------ | -------------------------------------------------------------------------------------- |
| enabled | boolean | false | Turn syntax highlighting on. |
| theme | Partial<CodeHighlightTheme> | GitHub-light | Partial override merged over the default theme. Values are RRGGBB hex. |
| languages | string[] | common | Whitelist of language grammars to load. Excluded/unknown languages fall back to plain. |
| showLanguageLabel | boolean | true | Render the language name as a bold label above the block. |
TextReplacement
| Field | Type |
| --------- | ------- |
| find | string |
| replace|string |
Errors
All conversion failures throw MarkdownConversionError (exported from the root). The error exposes a typed context field (e.g. { orientation, sectionIndex }) to make debugging easier.
Requirements
- Node.js ≥ 18 (ESM-only package)
- Browsers: any evergreen browser that supports ES2020 + Blobs
Install as an agent skill
For use in agent environments that speak the skills CLI:
# Quick add from GitHub URL
npx skills add https://github.com/mohtashammurshid/md-to-docx --skill md-to-docx
# From GitHub shorthand, pinned to an agent
npx skills add MohtashamMurshid/md-to-docx --skill md-to-docx --agent cursor --yes --full-depth
# From a local clone
npx skills add . --skill md-to-docx --agent cursor --yes --full-depth
# Discover before installing
npx skills add MohtashamMurshid/md-to-docx --list --full-depthDevelopment
git clone https://github.com/MohtashamMurshid/md-to-docx.git
cd md-to-docx
npm install
npm run build # compile TypeScript to dist/
npm test # run the Jest suite (4 suites, 45 tests)Tests run offline against generated Word XML using JSZip. Set DEBUG_DOCX=1 to have tests also write .docx artifacts under test-output/ for manual inspection.
Changelog
See CHANGELOG.md for a detailed history.
Contributing
PRs and issues are welcome. A few guidelines:
- Open an issue to discuss non-trivial changes before sending a patch.
- Add or update tests under
tests/— the suite uses XML-level assertions on the generated DOCX, so changes to rendering should be verified at that level. - Keep the public API surface stable; internal refactors should not require bumping the major version.
- Run
npm run build && npm testbefore pushing.
License
MIT © Mohtasham Murshid Madani
Built on top of [docx](https://www.npmjs.com/package/docx), [unified](https://unifiedjs.com), [remark](https://github.com/remarkjs/remark), and [lowlight](https://github.com/wooorm/lowlight).
