Write your CV in Markdown; get a styled, print-ready PDF, a responsive web page, and a Markdown README out the other end. One source of truth, many outputs — and a tidy way to keep role-specific variants without copy-pasting.
This repo ships with a fictional Joe Bloggs CV as a worked example. Fork it, swap in your own details, and build.
src/cvs/main.md ──┐
│ resolve {{> partials}} → one Markdown doc
src/sections/* ───┘ │
▼
marked → HTML → Playwright → PDF
│
index.html / debug.html / (optional README.md)
Each CV is a single main file in src/cvs/ that composes shared sections
from src/sections/ using Handlebars-style partials ({{> header/main }}). The
engine resolves the partials, renders Markdown to HTML, applies your chosen
stylesheet(s), and prints a PDF with Playwright.
Prerequisites: Node.js 20 (nvm use to match .nvmrc)
and the Playwright Chromium browser.
npm install
npx playwright install chromium # one-time, needed for PDF generation
npm run buildThis builds the primary variant and writes:
| File | What it is |
|---|---|
joebloggs_cv.pdf |
Print-ready PDF (the canonical output) |
index.html |
Self-contained responsive web version |
debug.html |
HTML with visible page boundaries, for tweaking layout |
The output base name (joebloggs_cv) comes from outputName in cv.config.js.
- Your details — edit
cv.config.js(meta: name, email, URL, etc.) and the section files undersrc/sections/(header, introduction, experience, skills, education, awards, about me). - Compose the CV —
src/cvs/main.mddecides which sections appear and in what order. Add, remove, or reorder the{{> ... }}partials. - Build —
npm run build, then openindex.htmlor the PDF.
src/sections/_template.md is a full reference for the available formatting,
CSS classes, page breaks, icons, and entry formats. Start there when writing
content.
Define more variants in the cvs object in cv.config.js. A general-purpose
variant is just a main file in src/cvs/. A variant tailored to a specific job
lives in src/applications/<name>/ and keeps everything for that application
together:
cv.md— the tailored CV (the only file that gets built)jd.md— the source job description (reference only, never rendered)cover-letter.md— your cover letter draft (reference only)
See src/applications/example/ for a worked example. Build a specific variant
by passing its key:
npm run build -- exampleNon-primary variants write joebloggs_cv_<variant>.pdf and never overwrite the
primary's index.html / README.md.
Two stylesheets ship in src/styles/: cv (default) and newspaper. Choose
per variant via style: ["cv", "newspaper"] in its overrides. Edit the CSS or
add your own stylesheet and reference it by filename.
By default npm run build does not touch README.md (so this documentation
is safe). If you want your CV to render into the README — handy for a
github.com/<you>/<you> profile repo — set readme: true in the primary
variant's overrides.
npm run watch # rebuild on every change under src/
npm test # run the Jest test suite
npm run lint # ESLint
npm run spellcheck # cspell over CV markdown (add words to cspell.config.json)
npm run validate # lint + testCI (.github/workflows/ci.yml) runs lint, spellcheck, tests with coverage, a
security audit, and a full build on every push and PR.
index.html is fully self-contained (inlined CSS), so any static host works —
drop it on Vercel, Netlify, or GitHub Pages. Point downloadLink in
cv.config.js at wherever you publish the PDF to wire up the web download button.
cv.config.js # who you are + which variants to build
src/
createCV.js # entry point / CLI
cvs/ # main files (one per general-purpose variant)
applications/ # role-specific variants (cv.md + jd.md + cover-letter.md)
sections/ # reusable content partials + icons + _template.md
styles/ # cv.css, newspaper.css
linkedin.md # plain-text LinkedIn profile source (not built)
generate/ # the HTML + PDF engine
A Markdown-first CV builder. Originally a simple Markdown-to-PDF script, now a small composable engine. Contributions and forks welcome.
