mdsnap
v1.0.0
Published
A CLI tool to convert Markdown to images. Support batch conversion, multiple themes, and custom CSS.
Maintainers
Readme
mdsnap
A CLI tool to convert Markdown to images. Supports batch conversion, multiple themes, and custom CSS. The CLI binary is named mdsnap.
Features
- 🚀 CLI Tool - Simple command-line interface
- 📦 Batch Conversion - Convert entire directories of markdown files (subfolder structure preserved)
- 🎨 7 Themes - gradient-card / minimal / dark / notebook / magazine / blueprint / custom CSS
- 📝 Full Markdown Support - GFM, code highlighting, tables, blockquotes
- 💡 Callouts - GitHub / Obsidian style admonitions like
> [!warning] - 🔗 Wikilinks - Obsidian-style
[[double-bracket]]references - 🖼️ Multiple Formats - PNG, JPEG, WebP output
Installation
npm install -g mdsnapOr use directly with npx:
npx mdsnap input.md -o output.pngThe npm package is
mdsnap. Once installed, the CLI binary is available asmdsnap.
Browser Requirement
mdsnap uses a headless browser (via Puppeteer) to render markdown.
When you npm install the package, Puppeteer normally downloads its bundled Chrome for Testing — that's the simplest path and requires no extra setup. At runtime the CLI looks for a browser in this order:
PUPPETEER_EXECUTABLE_PATHenvironment variable- Auto-detected system Chrome / Edge / Chromium
- Puppeteer's bundled Chrome for Testing
If you skipped the bundled download (e.g. set PUPPETEER_SKIP_DOWNLOAD=true in CI) and don't have a system browser, install one with:
npx puppeteer browsers install chromeOr point at an existing browser:
export PUPPETEER_EXECUTABLE_PATH="/path/to/chrome"Usage
Single File
# Basic usage
mdsnap input.md -o output.png
# With theme
mdsnap input.md -o output.png --theme dark
# Custom width
mdsnap input.md -o output.png --width 1200
# With header and footer
mdsnap input.md -o output.png --header "My Blog" --footer "© 2024"Batch Conversion
# Convert all markdown files in a directory
mdsnap ./docs -o ./images
# With theme and custom settings
mdsnap ./posts -o ./output --theme gradient-card --width 800Input/output directory layout
Batch mode walks **/*.md under the input directory and mirrors the relative
path in the output directory. So a layout like
docs/
index.md
lesson01/
intro.md
practice.md
lesson02/
intro.mdrun with mdsnap ./docs -o ./images --format webp produces
images/
index.webp
lesson01/
intro.webp
practice.webp
lesson02/
intro.webpFiles with the same basename in different folders never collide. Output directories are created on demand.
Theme gallery for one sample
When you want to render one markdown file through every built-in theme to compare, the convention used in this repo is:
samples/ ← your input markdown(s)
lesson01.md
samples-out/ ← rendered output, grouped by sample name
lesson01/
gradient-card.png
minimal.png
dark.png
notebook.png
magazine.png
blueprint.png
prose.png
sepia.png
docs.png
report.pngThis is what npm run gallery produces. It reuses one browser instance across all
themes, so 10 themes finish in roughly the time of 1-2 single CLI invocations:
# Render every theme of one markdown into samples-out/<sample-name>/
npm run gallery -- samples/lesson01.md
# Pick a format
npm run gallery -- samples/lesson01.md --format webp
# Or only specific themes
npm run gallery -- samples/lesson01.md --theme prose --theme sepiaThe samples/ and samples-out/ directories are git-ignored — they are a personal
workspace, not part of the published package.
Custom CSS
# Use custom CSS file
mdsnap input.md -o output.png --css ./my-style.cssSmaller files (WebP / JPEG)
PNG output is the default and lossless. For long documents the file can grow to a few MB. Switch to WebP or JPEG with --format to shrink it dramatically:
# WebP — usually the smallest, still high quality
mdsnap input.md -o output.webp --format webp
# JPEG with quality knob (1-100, default 90)
mdsnap input.md -o output.jpg --format jpeg --quality 85In a real test on an 8.7 KB Chinese lesson markdown rendered at 800px wide:
| Format | Size | Notes | | --- | --- | --- | | PNG | ~1.1 MB | lossless, default | | JPEG q85 | ~700 KB | good for screenshots without code | | WebP | ~540 KB | best size/quality, modern browsers |
Extended Markdown
mdsnap ships with two non-standard syntaxes turned on by default:
Wikilinks
See [[网页]] for details.
Read [[课程脉络|the course map]] later.Both render as styled inline links with a data-target attribute, so downstream tooling can rewrite them into real URLs if needed.
Callouts (GitHub / Obsidian flavor)
> [!note]
> Helpful detail.
> [!warning] 常见误区
> Custom titles work too.Recognised types: note, tip, important, warning, caution, info, question, todo, success, failure, danger, bug, example, quote, abstract, summary. Each gets its own accent color in every theme.
CLI Options
| Option | Description | Default |
|--------|-------------|---------|
| -o, --output <path> | Output file or directory | ./output.png |
| -t, --theme <name> | Theme name | gradient-card |
| -w, --width <number> | Output width in pixels | 800 |
| -p, --padding <number> | Padding in pixels | 40 |
| --header <text> | Header text | - |
| --footer <text> | Footer text | - |
| -c, --css <path> | Custom CSS file path | - |
| -f, --format <format> | Output format (png, jpeg, webp) | png |
| -q, --quality <number> | Image quality for jpeg/webp | 90 |
Themes
gradient-card
Colorful gradient background with a white rounded card. Perfect for social media sharing.
minimal
Clean white document style. Suitable for documentation and printing.
dark
Dark mode theme. Great for code-heavy content.
notebook
Cream paper, blue ink accents, and a signature red left margin rule that runs the
full length of the page. Picks up the lined-paper aesthetic with a faint horizontal
rule pattern, auto § 01 section numbering on h2, and a serif drop-cap on the
opening paragraph. Use for: lesson notes, study guides, long-form writing.
magazine
Editorial layout with high-contrast display serif, a single bold editorial-red
accent, oversized h1, drop cap, pull-quote treatment for blockquotes, and a ❦
ornament in place of horizontal rules. Use for: essays, reviews, opinion pieces.
blueprint
Deep blueprint blue with a faint white grid, "white paper" inserts for tables and
code, condensed engineering type with § 02 · counters on h2, and a REV. 1.0
stamp in the corner. Use for: technical guides, runbooks, engineering posts.
prose
Long-form reading. Warm paper #FAFAF7, Charter-family serif body, content
column capped at ~70ch with line-height 1.85. Callouts are quiet tinted blocks
with no thick coloured bars; wikilinks become dotted underlines. Use for: essays,
think pieces, slow reading.
sepia
Kindle-style protected reading. Aged paper #F4ECD8 with deep brown ink
#3A2F25, Georgia-family serif at ~64ch, no zebra rows, no painted heading
rules. The only flourish is a half-line drop cap on the first paragraph. Use
for: pure long text, ebook-feel posts.
docs
Technical reference. Inter sans-serif, every h2 gets a 4px coloured anchor bar
to the left for fast scanning, code blocks open with a dark "CODE" / language
chrome strip, tables are first-class with a tinted header row. Use for: tutorials,
API notes, changelogs.
report
Analytical brief. Off-white #FBFBF8, Inter body with automatic 1. / 1.1
section numbering on h2 / h3 (CSS counters), a monospaced uppercase metadata
strip at the top via the --header slot, and double-rule consulting-style
tables. Use for: research notes, internal briefs, structured long documents.
custom
Use your own CSS file for complete customization.
API Usage
You can also use mdsnap as a library:
import { renderToImage, renderToBuffer, convert } from 'mdsnap'
// Render to file
const result = await renderToImage({
markdown: '# Hello World\n\nThis is a test.',
output: './output.png',
theme: 'gradient-card',
width: 800,
})
console.log(result) // { path: './output.png', width: 800, height: 600 }
// Render to buffer
const { buffer, width, height } = await renderToBuffer({
markdown: '# Hello World',
theme: 'dark',
})
// Or run a full batch conversion programmatically
const summary = await convert(
{ input: './docs', output: './images', theme: 'dark' },
{ render: renderToImage }
)
console.log(`${summary.succeeded}/${summary.results.length} files converted`)License
MIT License - feel free to use in your projects.
