md2pdf-th
v4.0.3
Published
Markdown to PDF library & CLI — Thai/English, dark theme, TOC, cover, merge, watch, NestJS module
Maintainers
Readme
md2pdf-th is an open-source Markdown-to-PDF converter with first-class Thai language support. It works as a CLI tool, Node.js library, and NestJS module — featuring auto font detection, YAML frontmatter, HTML sanitization, batch conversion, watch mode, web preview, and PDF merging. Powered by Puppeteer via md-to-pdf with pdf-lib for metadata, watermarks, and merging.
Latest Release: v4.0.3 — 19 bug fixes (3 Critical + 5 High + 6 Medium + 5 Low), 12 new regression tests, JSDoc documentation for all functions. See CHANGELOG.md.
Table of contents
- What's new in V4
- Quick start
- Why md2pdf-th?
- CLI reference
- Library API
- NestJS integration
- API reference
- Docker
- GitHub Action
- Project structure
- Requirements
- Contributing
- License
What's new in V4
V4 brings a complete rewrite of the core engine with Thai-first design, YAML frontmatter options, built-in templates, PDF watermarking, web preview server, and NestJS integration.
| Area | Change | Impact | |------|--------|--------| | V4.0.3 Bug Audit | 19 bug fixes — frontmatter merge, XSS sanitizer, action.yml RCE, path traversal, watermark API | Stability ⬆️ | | V4.0.2 Concurrency | Deadlock fix, rate limit hardening, path traversal protection | Reliability ⬆️ | | V4.0.0 Rewrite | New core engine, frontmatter options, templates, watermark, web preview, NestJS module | Features ⬆️ |
Critical: Frontmatter merge ignoring CLI flags (_explicitKeys), sanitizeHtml XSS bypass (unclosed tags), action.yml expression injection (RCE)
High: Image serving path traversal, web-ui.html broken import, output path detection, --no-page-numbers + --header conflict, embedFont wrong API
Medium: Regex EOF/sync issues, quoted value parsing, null guard, NestJS content override, CI split
Low: TypeScript types, import path comment, Tabloid dropdown
Plus 12 regression tests and JSDoc for all functions.
Quick start
# Using npx (no install)
npx md2pdf-th document.md
# Global install
npm install -g md2pdf-th
md2pdf-th document.md
# Dark theme + TOC + cover
md2pdf-th --theme dark --toc --cover doc.md
# Use as library
const { md2pdfTh } = require('md2pdf-th');
const pdf = await md2pdfTh({ content: '# สวัสดี', theme: 'dark' });docker build -t md2pdf-th .
docker run --rm -v $(pwd):/data md2pdf-th /data/doc.md /data/output.pdfInstallation Options
Lightweight install (skip Chromium download)
If you already have Chromium/Chrome on your system:
# Skip Puppeteer's Chromium download (~150MB saved)
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install -g md2pdf-th
# Point to system Chrome
export PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
md2pdf-th doc.md| Platform | System Chrome Path |
|----------|-------------------|
| Linux (Debian/Ubuntu) | /usr/bin/chromium or /usr/bin/chromium-browser |
| macOS | /Applications/Google Chrome.app/Contents/MacOS/Google Chrome |
| Windows | C:\Program Files\Google\Chrome\Application\chrome.exe |
Docker (no install needed)
# Build once, reuse
# docker build -t md2pdf-th .
docker run --rm -v $(pwd):/data md2pdf-th /data/doc.md /data/output.pdfCI environments
# CI environments need --no-sandbox (auto-detected via CI=true env)
CI=true md2pdf-th doc.mdWhy md2pdf-th?
The problem
Converting Markdown to PDF with proper Thai language support is painful — most tools break Thai line wrapping, use wrong fonts, or lack professional formatting options. Developers end up fighting with LaTeX or building custom Puppeteer scripts.
The solution
md2pdf-th is a zero-config Markdown to PDF converter that handles Thai and English content out of the box:
- 🇹🇭 Thai-First Design — Auto-detect Thai content ratio, smart font fallback (Leelawadee → Noto Sans Thai → Tahoma)
- 🎨 Professional Output — Dark theme, 3 built-in templates (resume, report, invoice), custom fonts
- 📑 Document Features — TOC, cover page from frontmatter, header/footer, page numbers, watermark
- ⚡ Developer Experience — CLI, library API, NestJS module, watch mode, web preview, batch conversion
- 🔒 Security — HTML sanitization (XSS), path traversal protection, auth token for web server
- 🐳 CI/CD Ready — Docker image, GitHub Action, TypeScript types
CLI reference
md2pdf-th <file.md> [output.pdf]
md2pdf-th <file1.md> <file2.md> ... # batch convert
md2pdf-th [options] <file.md>Options
| Option | Description |
|--------|-------------|
| --css <path> | Custom CSS file path |
| --outdir, -o <dir> | Output directory |
| --no-page-numbers | Disable page numbers |
| --theme <light\|dark> | Color theme (default: light) |
| --toc | Generate Table of Contents |
| --watch | Watch mode — reconvert on file change |
| --merge | Merge multiple PDFs into one |
| --cover | Add cover page from frontmatter |
| --header <text> | Custom header text |
| --footer <text> | Custom footer text |
| --format <size> | Page size: A3, A4, A5, Letter, Legal, Tabloid |
| --font <name> | Custom font family |
| --template <name> | Built-in template: resume, report, invoice |
| --watermark <text> | Diagonal watermark text |
| --output-filename <pattern> | Output filename pattern: {name}, {date}, {time}, {timestamp} |
| --concurrency <n> | Batch concurrency limit 1-32 (default: 4) |
| --serve | Start web preview server |
| --port <port> | Server port (default: 3000) |
| --html-only | Export HTML instead of PDF (lightweight, no Puppeteer) |
| --timeout <ms> | Conversion timeout in milliseconds (default: 60000) |
| --version, -v | Show version |
| --help, -h | Show help |
Examples
# Single file
md2pdf-th doc.md
# Dark theme
md2pdf-th --theme dark doc.md
# TOC + cover page
md2pdf-th --toc --cover doc.md
# Custom header/footer
md2pdf-th --header "Company" --footer "Confidential" doc.md
# Letter format with Georgia font
md2pdf-th --format Letter --font Georgia doc.md
# Merge multiple files
md2pdf-th --merge doc1.md doc2.md doc3.md
# Watch mode
md2pdf-th --watch doc.md
# Web preview
md2pdf-th --serve --port 8080 doc.md
# Resume template
md2pdf-th --template resume resume.md
# Report with watermark
md2pdf-th --template report --watermark "DRAFT" report.md
# Custom output filename pattern
md2pdf-th --output-filename "{name}-{date}" -o ./pdfs *.md
# Batch convert
md2pdf-th -o ./pdfs *.mdLibrary API
Use as a Node.js library — returns PDF Buffer:
const { md2pdfTh } = require('md2pdf-th');
// From markdown string → Buffer
const pdfBuffer = await md2pdfTh({ content: '# สวัสดี' });
// From file → Buffer
const pdfBuffer = await md2pdfTh({ inputPath: 'doc.md' });
// From file → save to disk
await md2pdfTh({ inputPath: 'doc.md', outputPath: 'out.pdf' });
// With options
const pdfBuffer = await md2pdfTh({
content: '# Hello',
theme: 'dark',
toc: true,
cover: true,
headerText: 'My Company',
footerText: 'Confidential',
format: 'Letter',
font: 'Georgia',
lang: 'th',
template: 'report',
watermark: 'DRAFT',
});Frontmatter (for --cover & options)
---
title: My Document Title
author: John Doe
date: 2024-01-01
tags: report, quarterly
description: Quarterly report
theme: dark
toc: true
cover: true
format: A4
template: report
watermark: DRAFT
headerText: Company Name
footerText: Confidential
---
# Content starts hereAll frontmatter options: title, author, date, tags, description, theme, toc, cover, format, template, watermark, headerText, footerText, noPageNumbers, font, lang
Custom CSS
Default stylesheet is bundled. Override with --css or css option:
md2pdf-th --css my-style.css doc.mdawait md2pdfTh({ content: '# Hello', css: 'h1 { color: red; }' });NestJS integration
import { Md2PdfModule, Md2PdfService } from 'md2pdf-th/nestjs';
@Module({
imports: [Md2PdfModule.forRoot({ theme: 'dark' })],
})
class AppModule {}
@Injectable()
class ReportService {
constructor(private md2pdf: Md2PdfService) {}
async generateReport() {
const pdf = await this.md2pdf.convertFromContent('# Report');
return pdf; // Buffer
}
}Md2PdfModule.forRootAsync({
useFactory: (config: ConfigService) => config.get('md2pdf'),
inject: [ConfigService],
})API reference
md2pdfTh(options) → Promise<Buffer>
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| content | string | — | Markdown content string (alternative to inputPath) |
| inputPath | string | — | Path to .md file (alternative to content) |
| outputPath | string | — | Output PDF path. If omitted, returns Buffer |
| css | string | — | Custom CSS string |
| cssPath | string | — | Path to custom CSS file |
| theme | 'light'\|'dark' | 'light' | Color theme |
| toc | boolean | false | Generate Table of Contents |
| cover | boolean | false | Add cover page from frontmatter |
| headerText | string | — | Custom header text |
| footerText | string | — | Custom footer text |
| format | string | 'A4' | Page size: A3, A4, A5, Letter, Legal, Tabloid |
| font | string | — | Custom font family |
| noPageNumbers | boolean | false | Disable page numbers |
| lang | 'th'\|'en' | 'th' | Language hint for font selection |
| template | string | — | Built-in template: resume, report, invoice |
| watermark | string | — | Diagonal watermark text |
| outputFilename | string | — | Output filename pattern ({name}, {date}, {time}, {timestamp}) |
mergePdfBuffers(buffers) → Promise<Buffer>
Merge multiple PDF buffers into one.
sanitizeHtml(content) → string
Strip dangerous HTML (script, iframe, SVG, event handlers, etc.).
parseFrontmatter(content) → FrontmatterMeta
Parse YAML frontmatter. Returns { title, author, date, tags, description, theme, toc, cover, format, ... }.
Docker
# Build
docker build -t md2pdf-th .
# Convert
docker run --rm -v $(pwd):/data md2pdf-th /data/doc.md /data/output.pdf
# With options
docker run --rm -v $(pwd):/data md2pdf-th --template report --toc /data/doc.mdGitHub Action
Use in your CI pipeline:
- uses: teeprakorn1/md2pdf-th@v4
with:
markdown-file: report.md
output-file: report.pdf
theme: dark
format: A4
toc: true
template: report
watermark: DRAFT- run: npm install -g md2pdf-th
- run: md2pdf-th --toc --cover report.mdProject structure
md2pdf-th/
├── md2pdf.js # CLI entry point
├── lib/
│ ├── md2pdf-core.js # Core engine (sanitize, TOC, cover, watermark, metadata)
│ └── nestjs/
│ ├── md2pdf.module.js # NestJS module (forRoot, forRootAsync)
│ ├── md2pdf.service.js # NestJS service wrapper
│ └── index.js # NestJS entry point
├── types/
│ ├── index.d.ts # TypeScript definitions (core)
│ └── nestjs.d.ts # TypeScript definitions (NestJS)
├── templates/
│ ├── resume.css # Resume template
│ ├── report.css # Report template
│ └── invoice.css # Invoice template
├── style.css # Light theme (CSS vars only)
├── style-dark.css # Dark theme (CSS vars only)
├── style-base.css # Shared layout rules
├── web-ui.html # Web UI (drag & drop)
├── md/ # Example markdown files
├── test/
│ ├── unit.test.js # Unit tests (55 tests)
│ ├── nestjs.test.js # NestJS module/service tests (8 tests)
│ └── md2pdf.test.js # Integration tests (19 tests)
├── action.yml # GitHub Action definition
├── Dockerfile # Docker with Chromium + Thai fonts
└── package.json # npm package configRequirements
- Node.js >= 16.0.0
- Chromium (auto-installed by Puppeteer via
md-to-pdf)
Documentation
- CHANGELOG.md — Version history and bug fixes
- USAGE.md — Extended usage guide, environment variables, troubleshooting
- CODEBASE.md — Architecture, file map, module responsibilities
- DEPLOY.md — Deployment guide (npm, Docker, CI/CD, NestJS)
Contributing
See CONTRIBUTING.md for guidelines.
Security
See SECURITY.md for vulnerability reporting.
Code of conduct
See CODE_OF_CONDUCT.md.
License
Apache-2.0 © Teepakorn Kumvong
