Comparison
VS Code Markdown to PDF: Settings, Extensions and Alternatives
Published July 30, 2026
VS Code is where a lot of Markdown gets written, so converting it there is an obvious wish. The reality is that VS Code has no native PDF export, the main extension is capable but has real rough edges, and for anything where layout matters the better answer is often to convert outside the editor.
This guide covers what actually works, the settings worth knowing, and where the boundary sits.
What VS Code does natively
Nothing, directly. VS Code has a built-in Markdown preview (Ctrl/Cmd+Shift+V) but no export command — no Save as PDF, no print. The preview is a webview and VS Code does not expose a print path to it.
There is one native-ish route worth knowing: the preview supports custom CSS via the markdown.styles setting, and the Developer: Open Webview Developer Tools command gives you a devtools window on the preview. From there you can trigger a print. This is a genuinely awkward path and not a workflow, but it is useful for debugging print CSS without installing anything.
Everything practical goes through an extension.
The extension landscape
| Extension | Engine | Strengths | Weaknesses |
|---|---|---|---|
| Markdown PDF (yzane) | Bundled Puppeteer | Most complete: PDF/HTML/PNG/JPEG, headers, footers, custom CSS | Large install; infrequently updated; Chromium download issues |
| Markdown Preview Enhanced (shd101wyy) | Puppeteer / Prince / wkhtmltopdf | Excellent preview, Pandoc integration, presentations, many export formats | Complex configuration; heavier |
| Markdown All in One (yzhang) | Browser print | Best editing experience: TOC, list continuation, table formatting | Export is via browser print of HTML — indirect |
| Pandoc wrappers | Local Pandoc | Full Pandoc feature set including citations | Requires Pandoc installed separately |
| Marp for VS Code | Marp CLI | Purpose-built for slide decks | Only for presentations |
The practical recommendation: Markdown All in One for writing (it makes editing Markdown genuinely better and does not try to be an exporter), plus one of the others when you need PDF. If you want a single extension that does everything, Markdown Preview Enhanced is the most capable and the most configuration.
Markdown PDF settings in depth
The most-used extension, and its settings are worth understanding because the defaults are not great.
Convert with Ctrl/Cmd+Shift+P → Markdown PDF: Export (pdf).
A reasonable configuration in settings.json:
{
// Page setup
"markdown-pdf.format": "A4",
"markdown-pdf.margin.top": "22mm",
"markdown-pdf.margin.bottom": "22mm",
"markdown-pdf.margin.right": "18mm",
"markdown-pdf.margin.left": "18mm",
"markdown-pdf.orientation": "portrait",
"markdown-pdf.printBackground": true,
// Headers and footers — Puppeteer templates, not CSS margin boxes
"markdown-pdf.displayHeaderFooter": true,
"markdown-pdf.headerTemplate": "<div style=\"font-size:8pt; color:#888; width:100%; text-align:center; padding-top:6mm;\"><span class='title'></span></div>",
"markdown-pdf.footerTemplate": "<div style=\"font-size:8pt; color:#888; width:100%; text-align:center; padding-bottom:6mm;\"><span class='pageNumber'></span> / <span class='totalPages'></span></div>",
// Rendering
"markdown-pdf.highlight": true,
"markdown-pdf.highlightStyle": "github.css",
"markdown-pdf.breaks": false,
"markdown-pdf.styles": ["./print.css"],
"markdown-pdf.includeDefaultStyles": true,
// Behaviour
"markdown-pdf.convertOnSave": false,
"markdown-pdf.outputDirectory": "./pdf",
"markdown-pdf.outputDirectoryRelativePathFile": true
}
Settings that deserve explanation
printBackground defaults to false, which strips every background colour — code block shading, table headers, callout fills. This is the single most impactful setting and the reason exports look washed out. Set it to true.
displayHeaderFooter with the template settings is how you get page numbers. Note these are Puppeteer header templates, not CSS @page margin boxes — the underlying engine is headless Chromium, which does not support margin boxes, as covered in the headers and footers guide.
Consequences: the templates need fully inlined styles (they do not inherit your stylesheet), and you must add padding to keep them off the content — the header sits at the very top of the page box otherwise. The magic spans Puppeteer recognises are pageNumber, totalPages, date, title and url.
breaks controls whether a single newline becomes a <br>. true matches GitHub comment behaviour; false matches standard Markdown. For documents, false is correct — otherwise the one-sentence-per-line writing style produces a hard break after every sentence.
styles takes an array of CSS paths — relative to the Markdown file, absolute, or https:// URLs. This is where your print CSS goes.
includeDefaultStyles keeps the extension’s baseline styles. Set it to false if you want complete control and are supplying a full stylesheet; leave it true if your CSS only adjusts things.
convertOnSave regenerates the PDF on every save. Useful for tight iteration, and it makes saving slow — each conversion launches a Chromium render.
outputDirectoryRelativePathFile resolves outputDirectory relative to the Markdown file rather than the workspace root. Matters in multi-folder workspaces.
Per-file overrides
Front matter overrides settings for one document:
---
pdf_options:
format: A4
margin: 30mm 20mm
printBackground: true
displayHeaderFooter: true
headerTemplate: |
<div style="font-size:8pt; text-align:center; width:100%;">
Quarterly Report — Confidential
</div>
---
# Quarterly Report
Useful when most documents share settings but one needs different margins.
Workspace configuration
For a repository where everyone should get identical output, commit .vscode/settings.json:
{
"markdown-pdf.styles": ["./docs/print.css"],
"markdown-pdf.printBackground": true,
"markdown-pdf.format": "A4",
"markdown-pdf.outputDirectory": "./build/pdf"
}
Everyone who opens the repository converts the same way. Add build/ to .gitignore.
Custom CSS
The extension applies your CSS to the rendered HTML before Puppeteer prints it, so ordinary print CSS works — with one caveat: because it goes through Chromium, @page margin boxes, string-set and target-counter() are unavailable. Page size and margins are controlled by the extension settings, not @page.
/* print.css — referenced from markdown-pdf.styles */
body {
font-family: "Source Serif 4", Georgia, serif;
font-size: 11pt;
line-height: 1.55;
color: #1a1a1a;
/* Do not set margins here — the extension controls them. */
padding: 0;
max-width: none;
}
h1, h2, h3, h4 {
font-family: "Inter", system-ui, sans-serif;
break-after: avoid;
page-break-after: avoid;
break-inside: avoid;
}
h1 { font-size: 21pt; }
h2 { font-size: 16pt; margin-top: 18pt; }
h3 { font-size: 13pt; }
p, li { orphans: 3; widows: 3; }
pre, table, blockquote, figure {
break-inside: avoid;
page-break-inside: avoid;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 9pt;
background: #f7f7f9;
border: 0.75pt solid #e4e4e7;
padding: 8pt 10pt;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
table { width: 100%; border-collapse: collapse; font-size: 9.5pt; }
thead { display: table-header-group; }
th, td { border: 0.5pt solid #ccc; padding: 4pt 6pt; text-align: left; }
th {
background: #f4f4f6;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
/* Manual page breaks. */
.page-break {
break-after: page;
page-break-after: always;
height: 0;
}
Two notes. max-width: none and padding: 0 on body counteract the extension’s default styles, which constrain width for screen reading — leave them and your content sits in a narrow column with wide empty margins inside the page margins.
Page breaks work via the .page-break class plus <div class="page-break"></div> in your Markdown. The extension passes raw HTML through, so this works.
Using the VS Code theme
The extension can render using your current editor theme, which is tempting and usually wrong — editor themes are dark, and a dark PDF is rarely what you want. See the typography guide on why dark-mode PDFs are usually a mistake.
If you want it anyway:
{
"markdown-pdf.includeDefaultStyles": false,
"markdown-pdf.styles": ["./dark-print.css"]
}
And handle the page background explicitly, since the page box outside body stays white.
Common failures and fixes
Chromium fails to download
The commonest problem with the Markdown PDF extension. It bundles Puppeteer, which downloads Chromium on first use — several hundred megabytes. On a restricted network, behind a proxy, or on a corporate machine with egress filtering, this fails and the extension reports an unhelpful error.
Point it at a Chrome you already have:
{
"markdown-pdf.executablePath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
}
// macOS
{
"markdown-pdf.executablePath": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
}
// Linux
{
"markdown-pdf.executablePath": "/usr/bin/google-chrome"
}
This also cuts the install size substantially and is worth doing even when the download works.
Images do not appear
Relative paths resolve against the Markdown file, and the extension’s rendering context sometimes differs. Things to check, in order:
- The path is relative to the
.mdfile, not the workspace root. - No leading
/— that means filesystem root. - Remote images may not finish loading; the extension has no
waitUntilcontrol. - On Windows, backslashes in paths — use forward slashes in Markdown.
If relative paths keep failing, data URIs are immune to path resolution entirely:
# Inline a PNG as a data URI
echo ")"
Verbose in source, and it always works.
Math does not render
The extension does not render math by default. Markdown Preview Enhanced does, via KaTeX or MathJax. If you need math and you are using Markdown PDF, you will need to pre-render it or switch extensions.
Mermaid diagrams do not render
Same situation. Markdown Preview Enhanced supports Mermaid; Markdown PDF does not. This is a common reason people end up with both installed.
Emoji render as boxes
Font fallback, not an extension problem — see the typography guide. Add emoji fonts to your CSS font stack.
Output has huge margins
The extension’s default styles constrain body width. Set max-width: none and padding: 0 on body, as above.
When to convert elsewhere
VS Code conversion is convenient when you are already in the editor and the document is simple. Three cases where it is the wrong tool:
When you need real paged-media features. Page numbers via CSS, running headers pulled from your headings, a table of contents with page numbers, per-page footnotes. All of these need Paged.js, WeasyPrint or Prince — none work through Chromium. The Puppeteer header templates are a partial substitute for page numbers only.
When you need citations. VS Code extensions do not process citations. That is Pandoc’s job — see the academic writing guide.
When you are iterating on layout. This is the practical one. The VS Code loop is: edit, run the export command, wait for Chromium to launch and render, open the PDF, look, repeat. That is ten to twenty seconds per iteration. Fitting a resume to one page or checking where a table breaks takes dozens of iterations.
A tool with a live paginated preview collapses that loop to zero — you see the page boundaries as you type. For layout-sensitive documents this is not a small difference; it is the difference between a five-minute task and a forty-minute one.
When the document is going to someone who judges it. A resume, a client proposal, an invoice. The extra control is worth leaving the editor for.
A pragmatic split
What many people settle on:
- Write in VS Code with Markdown All in One for the editing conveniences — TOC generation, list continuation, table formatting on save, keyboard shortcuts for bold and italic.
- Convert in VS Code for documentation, READMEs, internal notes: anything where the default layout is fine.
- Convert elsewhere for anything layout-sensitive or citation-bearing.
That keeps the fast path fast without pretending the editor is a typesetting tool.
Useful editing settings
Unrelated to export, but this is where Markdown-in-VS-Code questions usually lead:
{
"[markdown]": {
"editor.wordWrap": "on",
"editor.quickSuggestions": { "other": false, "comments": false, "strings": false },
"editor.defaultFormatter": "yzhang.markdown-all-in-one",
"editor.formatOnSave": true,
// Show trailing whitespace — two spaces is a hard line break in Markdown
"editor.renderWhitespace": "trailing",
"editor.rulers": [80],
"files.trimTrailingWhitespace": false
},
"markdown.extension.toc.levels": "2..3",
"markdown.extension.toc.updateOnSave": true,
"markdown.extension.orderedList.marker": "one",
"markdown.extension.italic.indicator": "_",
"markdown.extension.tableFormatter.enabled": true
}
files.trimTrailingWhitespace: false scoped to Markdown matters: two trailing spaces is Markdown’s hard-line-break syntax, and trimming them silently changes your document. The global setting should stay true for code.
markdown.extension.orderedList.marker: "one" writes 1. for every item rather than incrementing, so inserting a list item does not produce a diff on every line below it.
Try it yourself
For the layout-sensitive documents where the VS Code loop is too slow, the editor here gives you a live paginated preview — page breaks, headers and page counts update as you type, and export matches the preview exactly. It handles the things the VS Code extensions do not: real @page margin boxes, running headers, and a contents list with page numbers, because it runs on Paged.js rather than Chromium’s print path.
Related: Pandoc comparison for the CLI route, print CSS for layout, and best editors and converters for the wider landscape.
About this guide
Written and maintained by the developer of MarkdownToFile — the browser-based converter this site runs on. Techniques described here are tested against the engines named in the text (Chromium's print pipeline, Paged.js, WeasyPrint), and engine limitations are stated rather than glossed over. Corrections are welcome via the contact page; more about the project on the about page.
Try it yourself — free, no signup
Convert your Markdown to a polished PDF right in your browser.
Open the editor