Converts Markdown and HTML files to PDFs with token replacement, styling, and content processing features.
- Converts Markdown and HTML to PDF using Chrome/Puppeteer
- Replaces tokens like
{{DATE}}and{{DEVELOPER_NAME}} - Applies Inter font styling and consistent layout
- Handles PDF-only content sections
- Supports page breaks and special formatting
- Works on macOS, Linux, and Windows
# Install dependencies
npm install
# Convert files
node bin/2pdf.js README.md # converts README.md to README.pdf
node bin/2pdf.js document.md # converts any markdown file
node bin/2pdf.js webpage.html # converts HTML files too
node bin/2pdf.js file.md -o out.pdf # custom output name- Always uses 2pdf's Inter font styling and layout
- Supports all markdown features (headers, lists, code blocks, etc.)
- Token replacement and special content features
- With existing CSS: Preserves your styles (internal, external, inline)
- Without CSS: Falls back to 2pdf's default styling
- Token replacement works the same way
- Color theming injects CSS custom properties
Insert dynamic content using {{TOKEN_NAME}} placeholders:
# {{PROJECT_NAME}} Documentation
**Version:** {{PROJECT_VERSION}}
**Generated:** {{DATE}} by {{DEVELOPER_NAME}}{{DATE}}- Current date (2024-01-15){{DATE_LONG}}- Current date (January 15, 2024){{TIME}}- Current time (14:30){{YEAR}},{{MONTH}},{{DAY}}- Date components{{TIMEZONE}}- Current timezone (PST, EST, UTC, etc.){{USERNAME}},{{HOSTNAME}}- System info{{PROJECT_NAME}}- Auto-generated from directory name
Create 2pdf.config with your own tokens:
DEVELOPER_NAME=John Doe
PROJECT_VERSION=2.1.0
COMPANY_NAME=Acme CorpContent that only appears in the PDF:
<!-- PDF ONLY This text only appears in the PDF version -->Force a new page:
Content here...
<!-- PAGE-BREAK -->
Content on next page...Style a paragraph as a compact badge:
<!-- live-site-shield -->
Live Site: https://example.comLive Site: https://example.com
Highlight important text using either HTML <mark> tags or the ==highlight== syntax:
This is <mark>highlighted text using HTML</mark> in a sentence.
This is ==highlighted text using double equals== in a sentence.Both syntaxes produce identical results and work inline within paragraphs, headers, lists, and other text elements.
Highlight entire sections (headers, paragraphs, lists, etc.) using the <highlight> tag:
<highlight>
### Section Title
This entire section is highlighted with a background color.
- List item 1
- List item 2
- List item 3
You can include multiple paragraphs, lists, headers, and other elements.
</highlight>- It's a custom HTML element that won't render in standard markdown preview
- It will be properly styled in the generated PDF
- Use blank lines before and after the opening/closing tags for proper markdown parsing
- Headers inside highlight blocks maintain their theme colors
- You can nest inline highlighting (
<mark>or==text==) inside block highlights
<!-- highlight-color: #e3f2fd -->
This text will be ==highlighted in light blue== instead of the default color.
<highlight>
This entire block will also use the light blue background.
</highlight>The default highlight color is #ffffad (light yellow), but you can customize it per document using the <!-- highlight-color: --> setting. This applies to both inline (<mark>, ==text==) and block (<highlight>) highlighting.
All headers use the Inter font family and are styled with specific colors, sizes, and formatting:
- Font size: 2.5rem (largest)
- Font weight: 300 (light)
- Color: Primary theme color (
--theme-color-primary) - Horizontal rule: Yes - thin line below in primary theme color
- Use for: Document title, major section dividers
- Font size: 1.75rem
- Font weight: 600 (semi-bold)
- Color: Primary theme color (
--theme-color-primary) - Horizontal rule: Yes - thin line below in primary theme color
- Use for: Main sections, chapter headings
- Font size: 1.4rem
- Font weight: 600 (semi-bold)
- Color: Secondary theme color (
--theme-color-secondary) - Horizontal rule: Yes - thin gray line (#aaa)
- Use for: Subsections within major sections
- Font size: 1.25rem
- Font weight: 600 (semi-bold)
- Color: Secondary theme color (
--theme-color-secondary) - Horizontal rule: No
- Use for: Smaller subsections, topic headings
- Font size: 1rem (same as body text)
- Font weight: 800 (extra bold)
- Color: Primary theme color (
--theme-color-primary) - Horizontal rule: No
- Use for: Minor topics, list section headers
- Font size: 0.7rem (smaller than body text)
- Font weight: 800 (extra bold)
- Color: Primary theme color (
--theme-color-primary) - Text transform: UPPERCASE
- Horizontal rule: No
- Use for: Labels, metadata headers, fine print sections
Headers use theme colors that can be customized per document:
<!-- theme-color-primary: #1976d2 -->
<!-- theme-color-secondary: #388e3c -->- Primary color affects H1, H2, H5, H6
- Secondary color affects H3, H4
Create a markdown table of contents with links that work in PDFs:
## Table of Contents
- [Introduction](#introduction)
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
## Introduction
Your introduction content here...
## Getting Started
### Installation
Installation steps...
etc.When converted to PDF, all links in the table of contents become clickable internal links that jump to the corresponding sections. PDF viewers will navigate to the correct page automatically.
- Headers are automatically assigned unique IDs (e.g.,
# Introduction→id="introduction") - Markdown links like
[Introduction](#introduction)are converted to HTML anchor links - PDF viewers recognize these internal links and allow navigation between pages
For long tables of contents or lists, you can display them in multiple columns:
## Table of Contents
<!-- two-columns -->
- [Introduction](#introduction)
- [Getting Started](#getting-started)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [Advanced Features](#advanced-features)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
<!-- /two-columns --><!-- three-columns -->
- [Item 1](#item-1)
- [Item 2](#item-2)
- [Item 3](#item-3)
- [Item 4](#item-4)
- [Item 5](#item-5)
- [Item 6](#item-6)
- [Item 7](#item-7)
- [Item 8](#item-8)
- [Item 9](#item-9)
<!-- /three-columns --><!-- four-columns -->
- [Item 1](#item-1)
- [Item 2](#item-2)
- [Item 3](#item-3)
- [Item 4](#item-4)
- [Item 5](#item-5)
- [Item 6](#item-6)
- [Item 7](#item-7)
- [Item 8](#item-8)
<!-- /four-columns -->The content between the opening and closing tags will be displayed in the specified number of columns with a subtle divider line between them.
You can force content to break to the next column using <!-- column-break -->:
<!-- three-columns -->
- [Item 1](#item-1)
- [Item 2](#item-2)
- [Item 3](#item-3)
<!-- column-break -->
- [Item 4](#item-4)
- [Item 5](#item-5)
- [Item 6](#item-6)
<!-- column-break -->
- [Item 7](#item-7)
- [Item 8](#item-8)
- [Item 9](#item-9)
<!-- /three-columns -->This ensures content is distributed exactly as you want across the columns.
Control table column widths with HTML comments:
<!-- col-widths: 30% 70% -->
| Name | Description |
| ---- | ------------------------------------------- |
| Item | Long description text that needs more space || Name | Description |
|---|---|
| Item | Long description text that needs more space |
<!-- col-widths: 100px 200px 50% -->
| ID | Name | Notes |
| --- | ---- | ---------- |
| 1 | Test | Some notes || ID | Name | Notes |
|---|---|---|
| 1 | Test | Some notes |
Override body-size for a single table. Place <!-- table-size: ... --> immediately before the table — it applies only to the next table, so you can use it multiple times throughout a document.
<!-- table-size: 1em 0.75em -->
| Name | Description |
| ---- | -------------- |
| Item | Body at 0.75em |- Two values: first is header (
th) size, second is body (td) size - One value applies to both:
<!-- table-size: 0.85em --> - Stackable with
col-widths:
<!-- col-widths: 30% 70% -->
<!-- table-size: 0.9em 0.75em -->
| Name | Description |
| ---- | ----------- |
| Item | Long text |Control table column alignment using standard markdown syntax with colons in the separator row:
:---= left aligned (default):---:= center aligned---:= right aligned
| Name | Description |
| ---- | ------------ |
| Item | Left aligned || Name | Status |
| :--: | :----: |
| Item | Active || Quantity | Price |
| -------: | -----: |
| 100 | $50.00 || Name | Status | Price |
| :-------- | :------: | ------: |
| Product A | Active | $99.99 |
| Product B | Inactive | $149.99 |You can combine alignment with column widths:
<!-- col-widths: 30% 40% 30% -->
| Name | Description | Price |
| :----- | :------------- | -----: |
| Item 1 | Left aligned | $10.00 |
| Item 2 | Center aligned | $20.00 |Override the width of a single image. Place <!-- img-width: ... --> immediately before the image — it applies only to the next image, so you can use it multiple times throughout a document.
<!-- img-width: 150px -->
- Any CSS length is accepted:
150px,50%,4in,12em, etc. - Images never overflow the page — the global
max-width: 100%still applies, so a width larger than the content area is clamped. - Standard markdown image syntax (
) is supported; the comment applies to whichever image follows it. Images without a preceding comment render at their native size (capped to the content width).
Override the text size of a single code block. Place <!-- code-size: ... --> immediately before the block — it applies only to the next code block, so you can use it as many times as you like in one document.
<!-- code-size: 1.4em -->
```js
const scaledUp = true;
```- Any CSS length is accepted:
1.4em,0.6em,11px,9pt, etc. - The value is relative to body text, the same way
table-sizeworks. The unstyled default for code blocks is0.75em, so1.4emis noticeably larger and0.6emnoticeably smaller. - Applies to fenced and indented code blocks. Inline code (
`like this`) is never affected — only the block that follows the comment. - Blocks without a preceding comment keep the default size.
H1, H2, and H3 get a hairline rule beneath them by default. Place <!-- rule: off --> on the line below a heading to drop that heading's rule.
## Section Without A Rule
<!-- rule: off -->
Body copy.- This is the only comment that targets the element above it — every other per-element tag (
table-size,img-width,code-size) applies to what follows. A blank line between the heading and the comment is fine either way. off,none,no,false, andhideall turn the rule off.<!-- rule: on -->does the reverse: it adds a rule to a heading that has none (H4-H6), drawn in the heading's own color.- Applies to one heading per comment, so use it as many times as you like. Headings without a comment keep the default.
- Markdown input only. HTML input keeps whatever its own CSS says.
Set theme colors, text colors, and font sizes directly in your document:
<!-- filename: custom-output-name.pdf -->
<!-- theme-color: #ff6b35 -->
<!-- theme-color-primary: #1434cb -->
<!-- theme-color-secondary: #27ae60 -->
<!-- body-color: #2c3e50 -->
<!-- link-color: #3498db -->
<!-- link-underline: off -->
<!-- highlight-color: #ffff00 -->
<!-- font-size: 1.2em -->
<!-- header-size: 1.3em -->
<!-- body-size: 0.9em -->
<!-- page-numbers: on -->
<!-- watermark: DRAFT -->
# Your Document Title
Content with custom styling...- filename: Custom PDF output filename (
report.pdf,my-document.pdf)- If empty or missing, defaults to input filename
- CLI
-ooption takes priority over this setting
- theme-color: Primary theme - headers, blockquotes, inline code (
#ff6b35,#1434cb,#27ae60) - theme-color-primary: Primary accent color for specific elements
- theme-color-secondary: Secondary accent color for specific elements
- body-color: All body text, paragraphs, lists (
#2c3e50,#333333) - link-color: Hyperlinks (
#3498db,#e74c3c) - highlight-color: Text highlighting background color (
#ffff00,#00ff00,#ff6b35)
- link-underline:
onoroff- controls link underlines
- font-size: Root font size (
1.2em,14px,16px) - header-size: All headers scale from this (
1.3em,1.1em) - body-size: Paragraphs, lists, tables (
0.9em,1.1em) - line-height: Line spacing for all text (
1.2em,1.5,18px) - paragraph-spacing: Margin above/below paragraphs (
0.8em,12px,1rem) - header-spacing: Margin above/below headers, scales by level (
1em,16px)- H1 has no top margin (abuts page margin)
- Headers after page breaks have no top margin
- Spacing scales: H2(1.0×), H3(0.9×), H4(0.75×), H5(0.6×), H6(0.5×)
- list-item-spacing: Margin above/below each list item (
0.2em,0.5em,8px)
- page-numbers:
on,off,X, orX of Y- controls footer page numbering - disclosure: Brief text for center footer (e.g.,
Internal Use Only,CONFIDENTIAL) - watermark:
on(orDRAFT),off, or any custom text - stamps a large diagonal watermark across every pageon/true/yesrendersDRAFT;off/false/no/nonedisables it- Any other value is used verbatim, e.g.
<!-- watermark: NOT FOR DISTRIBUTION --> - Drawn over the content in the
theme-color-primarycolor faded to 5% (falls back totheme-color), and scaled down automatically for longer strings - Ignored for
.htmlinput that carries its own CSS (same aslogo)
- logo: Filename (or absolute path) for a logo image displayed at the top of the document
- Bare filenames resolve to
public/assets/images/inside the 2pdf install - Embedded as a base64 data URI so the PDF stays portable
- Example:
<!-- logo: logo-feral.svg -->
- Bare filenames resolve to
- sequential-output:
onoroff- enables versioned output filenames - version-number:
XX.YYformat - auto-increments with each PDF generation - file-date:
BEFORE,AFTER, orOFF- stamps the output filename with a UTC timestamp- Format:
YYMMDDTHHMMZ(e.g.260514T2227Z) BEFOREprepends:260514T2227Z-document.pdfAFTERappends:document-260514T2227Z.pdf- Applies on top of any other filename source (CLI
-o,filename, or sequential version)
- Format:
When page numbers are enabled, the footer has three sections:
Left: Document title (from H1) | Date (DD MMM YYYY format) Center: Disclosure text (uppercase, optional) Right: Page numbers (X or X of Y format)
<!-- page-numbers: X -->
<!-- disclosure: Internal Use Only -->
# My Document TitleResults in footer: My Document Title | 25 Dec 2024 | INTERNAL USE ONLY | 1
Automatically version your PDF outputs with incremental version numbers:
<!-- sequential-output: on -->
<!-- version-number: 00.00 -->
# My Document- First run: Outputs
my-document-v00.00.pdf, updates version to00.01 - Second run: Outputs
my-document-v00.01.pdf, updates version to00.02 - Continues:
00.02→00.03→ ... →00.99→01.00→01.01
Version format: XX.YY (major.minor, 00-99 each)
Auto-increment: Source file is automatically updated with new version
Filename: document-vXX.YY.pdf format
Customize header and accent colors with the --color flag:
# Predefined colors (from config file)
node bin/2pdf.js doc.md --color blue
node bin/2pdf.js doc.md --color corporate
# Custom hex colors
node bin/2pdf.js doc.md --color 1434cb
node bin/2pdf.js doc.md --color "#ff6b35"Add predefined colors to your 2pdf.config:
COLOR_BLUE=#1434cb
COLOR_RED=#e74c3c
COLOR_GREEN=#27ae60
COLOR_CORPORATE=#2c3e50
COLOR_ORANGE=#ff6b35Generate PDFs as one continuous page instead of multiple US Letter pages:
node bin/2pdf.js file.md --single-page
node bin/2pdf.js file.md --single-page --color blueThis is perfect for:
- Web page captures
- Long documents that look better as continuous scrolls
- Documents with content that shouldn't be split across pages
node bin/2pdf.js file.md -s custom.css # custom CSS file
node bin/2pdf.js file.md --debug # keep temp files
node bin/2pdf.js file.md --verbose # detailed output<!-- ── Content layout ── -->
<!-- PDF ONLY Content only in PDF -->
<!-- PAGE-BREAK -->
<!-- live-site-shield -->
<!-- col-widths: 30% 70% -->
<!-- table-size: 1em 0.75em -->
<!-- img-width: 150px -->
<!-- code-size: 1.4em -->
<!-- rule: off -->
<!-- two-columns -->
<!-- /two-columns -->
<!-- three-columns -->
<!-- /three-columns -->
<!-- four-columns -->
<!-- /four-columns -->
<!-- column-break -->
<!-- ── Output file ── -->
<!-- filename: custom-output.pdf -->
<!-- file-date: AFTER -->
<!-- sequential-output: on -->
<!-- version-number: 00.00 -->
<!-- ── Colors ── -->
<!-- theme-color-primary: #880088 -->
<!-- theme-color-secondary: #666666 -->
<!-- body-color: #222222 -->
<!-- link-color: #0000ff -->
<!-- link-underline: off -->
<!-- highlight-color: #ffff00 -->
<!-- ── Typography ── -->
<!-- font-size: 1.2em -->
<!-- header-size: 1.3em -->
<!-- body-size: 0.9em -->
<!-- line-height: 1.2em -->
<!-- paragraph-spacing: 0.8em -->
<!-- header-spacing: 1em -->
<!-- list-item-spacing: 0.3em -->
<!-- ── Page features ── -->
<!-- logo: logo-feral.svg -->
<!-- page-numbers: X of Y -->
<!-- disclosure: Internal Use Only -->
<!-- watermark: DRAFT -->
# Text Highlighting
Inline: Use <mark>HTML mark tags</mark> or ==double equals syntax==.
Block: Use <highlight>custom tag</highlight> for entire sections.Create 2pdf.config with key=value pairs:
# Project info
PROJECT_VERSION=2.1.0
DEVELOPER_NAME=Jane Smith
COMPANY_NAME=Acme Corp
# Color themes
COLOR_BRAND=#1434cb
COLOR_ACCENT=#ff6b35
# Custom content
FOOTER_TEXT=Generated on {{DATE}} by {{DEVELOPER_NAME}}2pdf searches for 2pdf.config in this order:
- Same directory as the input file
- Current working directory
- 2pdf installation directory
config/subdirectory in any of the above
- Multiple passes: Tokens can reference other tokens
- Case sensitive:
{{NAME}}≠{{name}} - No whitespace: Use
{{TOKEN}}, not{{ TOKEN }} - Nested replacement:
GREETING=Hello {{USERNAME}}works - Missing tokens: Left unchanged for debugging
# 1. Create config file
echo "PROJECT_VERSION=2.1.0" > 2pdf.config
echo "DEVELOPER_NAME=Jane Smith" >> 2pdf.config
# 2. Use in markdown
echo "# Project v{{PROJECT_VERSION}} by {{DEVELOPER_NAME}}" > doc.md
# 3. Convert
node bin/2pdf.js doc.md
# Result: "Project v2.1.0 by Jane Smith" in the PDF© {{YEAR}} {{COMPANY_NAME}} • MIT License