Turns documents from Craft.do into clean, print-ready HTML and PDF files, using the official Craft API.
Default page size: 170 × 240 mm, two columns · Output: HTML (browser print) or PDF (Puppeteer)
Two ways to use it:
| Tool | Use it for |
|---|---|
CLI (craft-print-cli.js) |
One-off conversions on the command line, good for scripts |
HTTP server (craft-print-server.js) |
A running service with URLs for preview and PDF download |
- Requirements
- Quick start
- Set up the Craft API
- Using the CLI
- Using the server
- Finding the document ID
- Project layout
- The
private/folder - Changing the layout
- Supported Markdown
- Troubleshooting
- For developers
- License
- Node.js 22.12 or newer (
node --version) – required by Puppeteer 25 - A Craft workspace where you can create an API connection
- About 400 MB of disk space –
npm installdownloads its own copy of Chrome for Testing for PDF generation (Puppeteer).
With Git:
git clone https://github.com/<YOUR-GITHUB-NAME>/PrintReport.git
cd PrintReportWithout Git: on GitHub click Code → Download ZIP, unzip it, then open the folder.
npm installCopy the template into the private/ folder and fill it in:
cp .env.example private/.envThen open private/.env and enter the values from Craft
(see Set up the Craft API):
CRAFT_API_TOKEN=your-token-here
CRAFT_API_ENDPOINT=https://connect.craft.do/links/YOUR-CONNECTION-ID/api/v1
PORT=3000The
.gitignorefile excludes the wholeprivate/folder from Git, so your token, images, signatures and finished reports never leave your machine. A classic.envin the project root works too (also ignored).
node craft-print-cli.js <DOCUMENT-ID> --title "My Report" --pdf --output private/output/my-reportResult: private/output/my-report.pdf
The tool reads your documents through a Craft API connection. You create the connection inside the Craft app and it only ever exposes the documents you pick – the rest of your workspace stays private.
- Open Craft (desktop or web) and go to the Connections tab in the left sidebar. In some versions this sits under Imagine → API.
- Click "Add Your First API Connection" (or add another connection).
- Give it a name, for example
print-server. - Click "Add Document" and select every document you want to convert. You can add more documents to the same connection later.
Alternatively you can connect all documents or all daily notes – but a connection limited to specific documents is the safer choice.
At the top of the connection Craft shows:
- an API URL in the form
https://connect.craft.do/links/<CONNECTION-ID>/api/v1 - an API token – use the Set token / Copy control next to the URL
<CONNECTION-ID> is the random ID Craft generated for this connection (the same
string that appears in the URL). Copy both values.
The "Download AI Bundle" button gives you the full API reference plus a snapshot of the connected documents – handy for exploring the API.
CRAFT_API_TOKEN=<the token you copied>
CRAFT_API_ENDPOINT=https://connect.craft.do/links/<CONNECTION-ID>/api/v1
PORT=3000Internally the tool sends the token as Authorization: Bearer <token> and calls
GET <endpoint>/blocks?id=<document-id> to fetch a document as Markdown.
| Problem | Fix |
|---|---|
Unauthorized / HTTP 401 |
Token expired or revoked – open the connection in Craft, set a new token, update private/.env. |
| HTTP 404 | Wrong document ID, or that document is not part of this connection – add it under "Add Document". |
Official docs: https://connect.craft.do/api-docs · https://support.craft.do/en/integrate/api
node craft-print-cli.js <command> [options]| Command | Meaning |
|---|---|
<document-id> |
Convert the document with this ID |
list-themes |
Show the available layout presets |
help |
Show help |
| Option | Default | Meaning |
|---|---|---|
--title "…" |
Document |
Document title |
--author "…" |
– | Author name |
--pdf |
– | Save as PDF (uses Puppeteer / Chromium) |
--html |
✔ | Print HTML to stdout |
--output FILE |
title | Target file name without extension |
--theme NAME |
standard |
Layout preset (see list-themes) |
--dry-run |
– | Only show what would happen |
--verbose |
– | Detailed output |
# Write HTML to a file
node craft-print-cli.js abc123 --title "Handbook" --html > private/output/handbook.html
# PDF with an author
node craft-print-cli.js abc123 --title "Report" --author "Jane Doe" --pdf \
--output private/output/report
# Just test (write nothing)
node craft-print-cli.js abc123 --pdf --dry-run --verbosePut recurring commands into a personal script inside private/
(that folder is never pushed to GitHub). A starting point is already in
private/make.sh:
./private/make.shnpm start # normal mode
npm run dev # auto-reload (nodemon)The server listens on http://localhost:3000 (or PORT from your .env).
| Route | Purpose |
|---|---|
GET /print/:documentId?title=…&author=… |
HTML preview in the browser (then Cmd/Ctrl + P) |
GET /print-pdf/:documentId?title=…&author=… |
Download the PDF directly |
GET /health |
Status check (JSON) |
Example:
http://localhost:3000/print/abc123xyz?title=My%20Handbook&author=Jane%20Doe
When printing from the browser: choose "Save as PDF", turn margins off if you want, and enable "Background graphics".
- From the share URL:
https://www.craft.do/s/<DOCUMENT-ID>– the part after/s/. - As a block / document UUID: for example
A1B2C3D4-0000-0000-0000-1234567890AB(available in Craft via Copy Link or theGET /blocksAPI call).
PrintReport/
├── craft-print-server.js # HTTP server + core functions (fetch, HTML, PDF)
├── craft-print-cli.js # command-line tool
├── print-themes.js # layout presets (page size, columns, fonts)
├── print-template.html # HTML/CSS skeleton for printing
├── examples.js # example integrations for developers
├── .env.example # credentials template
├── .gitignore # excludes private/, node_modules/, outputs
├── package.json
├── LICENSE
└── private/ # LOCAL ONLY – never on GitHub (see below)
├── .env # token & endpoint
├── make.sh # personal build script
├── images/ # your own images for reports
├── signatures/ # signature graphics
└── output/ # generated HTML / PDF reports
Everything personal and confidential lives in one place: private/. The
.gitignore file lists /private/, so Git ignores the entire folder – a
git push never uploads:
- the API token (
private/.env) - your own images (
private/images/) - signatures (
private/signatures/) - finished reports with personal content (
private/output/)
Anyone who clones the repository creates these files themselves, using
.env.example and private/README.md. The folders
images/, signatures/ and output/ may need to be re-created:
mkdir -p private/images private/signatures private/outputThe print parameters live in craft-print-server.js in
the config object:
const config = {
pageWidth: 170, // mm
pageHeight: 240, // mm
margins: { top: 20, right: 20, bottom: 40, left: 40 } // mm
};Fine-tuning (columns, font sizes, page breaks) is in the CSS of
print-template.html:
.print-content { column-count: 1; column-gap: 1.5rem; } /* number of columns */
.heading-1 { font-size: 2.4rem; } /* main heading */
.text-body { font-size: 0.95rem; line-height: 1.4; } /* body text */print-themes.js contains ready-made format presets
(standard, technical, novel, academic, magazine, catalog), each with
its own page size, column count and typography. List them with
node craft-print-cli.js list-themes.
The server renders with marked (GitHub-flavored):
- Headings
#…#### - Bold
**…**, italic*…* - Lists (ordered and unordered)
- Inline code
`…`and code blocks``` - Quotes
> … - Links
[text](url) - Tables (GitHub-flavored Markdown)
Web images (https://…) are downloaded and embedded automatically for PDF
generation. Formats that Chromium cannot render (TIFF, BMP) are converted to
JPEG with sharp.
Page-break control inside the Craft document (as an HTML block):
<div class="break-before"></div> <!-- force a page break -->
<div class="no-break"> … </div> <!-- prevent a page break -->| Symptom | Cause / fix |
|---|---|
CRAFT_API_TOKEN is not set |
private/.env is missing or empty – run cp .env.example private/.env |
Unauthorized / HTTP 401 |
token expired, or the document is not shared in the API connection |
| Puppeteer will not start (Linux) | install the Chrome libraries: sudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libasound2 |
Could not find Chrome after install |
run npx puppeteer browsers install chrome |
| PDF is blank / has no colors | enable "Background graphics" when printing from the browser |
| Columns too narrow | increase pageWidth in config, or reduce column-count |
| Port already in use | change PORT in .env, or kill -9 $(lsof -t -i :3000) |
craft-print-cli.js ─┐
├─→ craft-print-server.js
examples.js ─────────┘ │
├─ fetchCraftContent(id) → Markdown from the Craft API
├─ transformMarkdownToHtml() → semantic HTML (marked)
├─ generatePrintHtml(content, meta)
│ └─ fills print-template.html
└─ htmlToPdf(html, path) → Puppeteer + image preload
const {
fetchCraftContent, // (documentId: string) => Promise<string> Markdown
generatePrintHtml, // (content: string, meta?) => string full HTML
htmlToPdf // (html: string, outputPath: string) => Promise<string>
} = require('./craft-print-server');print-themes.js exports themes, getTheme(name), listThemes() and
generateThemeCss(theme). More patterns (batch, caching, Express integration)
are shown in examples.js.
| Package | Version | Notes |
|---|---|---|
puppeteer |
^25 | bundles Chrome for Testing; needs Node ≥ 22.12 |
express |
^5 | server routes |
axios |
^1.20 | HTTP client for the Craft API and image downloads |
sharp |
^0.35 | converts TIFF/BMP images to JPEG for the PDF |
marked |
^11 | pinned – the custom renderer in transformMarkdownToHtml() uses the v11 positional-argument API; v12+ passes token objects instead |
npm audit is clean. Run npm outdated to see the pinned packages.
npm run dev # server with nodemon
npm run cli -- <document-id> --html --dry-runcraft-print-server.js loads private/.env on startup; if that file does not
exist, it falls back to .env in the project root. Both are listed in
.gitignore.
- Fork the repository, create a feature branch (
feat/short-description). - Never commit personal data – anything confidential belongs in
private/. - Open a pull request with a short description of its purpose.
MIT © 2026 Jörg Drees