notion-pdf — Notion page → Qraft-branded PDF
Renders a Notion page as a PDF with the Qraft letterhead and house style, faithful to the content: nothing is rewritten, condensed, or dropped. Only the letterhead and visual styling are added.
Use it to produce clean attachments (commercial proposals, client docs) when the email also links the Notion page: the PDF and the link must show the exact same content.
Files
| File | Role |
|---|---|
assets/style.css | The Qraft house style (typography, letterhead, callouts, tables, columns). Source of truth for the formatting. |
scripts/notion_md_to_pdf.py | Converter: Notion markdown → styled HTML → PDF. Pure stdlib Python. |
Workflow
- Fetch the page markdown:
mcp__notion__API-retrieve-page-markdownwith thepage_id. - Fetch the exact title (
API-retrieve-a-page,filter_properties=title) → it becomes the<h1>. - Resolve mentions: replace each
<mention-page url="..."/>with a markdown link[Page title](url)(fetch the linked page’s title). Otherwise the script emits a generic link. - Save the markdown to a
.mdfile (scratchpad). - Render:
uv run python scripts/notion_md_to_pdf.py INPUT.md OUTPUT.pdf \ --title "Exact Notion page title" \ --header-right "Proposition commerciale - Juin 2026"Add
--html-onlyto inspect the HTML without producing a PDF.
Fidelity rules (important)
- No content changes. Render the markdown as-is: same bullets, same figures, same order, same wording. No invented subtitle, no condensing, no adding/removing participants.
- The
QRAFT · <header-right>letterhead and the<h1>(page title) are the only added elements — that is the letterhead, not content. - Notion dividers (
---) are skipped (headings already carry a border). No text is lost. - After rendering, check that amounts/efforts in the PDF match Notion exactly.
Formatting mapping
- Notion headings:
#→<h2>,##→<h3>,###→<h4>(the<h1>is the page title). - Callouts:
blue_bg/yellow_bg/gray_bg/orange_bg→.callout.blueetc. Emoji icons are kept; single letter/digit Notion placeholders (r,t,1…) are dropped. - Tables: first row → header (
<th>, blue background);yellow_bgrow →tr.total(yellow);blue_bgrow →tr.subtotal(blue). Column widths (<colgroup>) are preserved. - Notion columns (
<columns>) →.cols(flex).
Requirements
- Chrome/Chromium installed (macOS: Google Chrome by default). The script auto-detects the binary; edit
CHROME_CANDIDATESotherwise. - No Python dependencies (stdlib only).