md-2-ats
v0.1.2
Published
Generate ATS-friendly CV/PDF from Markdown
Maintainers
Readme
Markdown 2 ATS
Write an ATS-friendly resume in Markdown, render it to PDF. Optimized for developers.
About
Markdown 2 ATS is a Node.js library that turns a Markdown resume into a clean, ATS-friendly PDF. It parses Markdown into a structured document, validates it against sensible resume rules, and renders a restrained single-column layout that parses reliably through Applicant Tracking Systems.
The default template is based on the ATS Standard, using a restrained layout, standard PDF fonts, and readable content structure so a resume works well for both applicant tracking systems and people.
It is a library, not an editor or a web app: you call it from your own script, CLI, or build step, and you stay in control of your files.
I can't guarantee that using this will improve your job search success rate. But I hope it helps.
See the Changelog for the complete project history and latest changes.
Notice
Output uses standard PDF fonts (Helvetica) by default, which only cover Latin-1 characters. To render CJK/Kana/Hangul text, provide your own TTF font (see Custom Fonts).
Features
- Write your resume in Markdown and render it to a clean PDF
- ATS-friendly single-column layout, based on the CareerCup template
- Automatic page breaking for multi-page resumes
- Profile photo support (PNG and JPEG)
- Custom TTF fonts for non-Latin text (e.g. Noto Sans CJK)
- Theming: font family, sizes, colors, and spacing
- Supports headings, lists (nested), blockquotes, thematic breaks, and inline formatting
- ESM and CommonJS builds with TypeScript types included
- CLI (
md-2-ats) for one-off renders - Works offline and never uploads your data anywhere
Install
pnpm add md-2-ats
# or: npm install md-2-atsRequires Node.js >= 18.
Usage
import { generateCV } from "md-2-ats";
import { readFile } from "node:fs/promises";
await generateCV({
input: "./cv.md",
output: "./cv.pdf",
profile: {
bytes: await readFile("./profile.png"),
size: 96,
position: "right",
},
});Options
| Option | Type | Description |
| --------- | -------------- | ----------------------------------------------------- |
| input | string | Path to the source Markdown file (required). |
| output | string | Path where the PDF is written (required). |
| profile | ProfileImage | Profile photo (bytes, optional size, position). |
| fonts | FontSet | Custom TTF fonts for non-Latin text (e.g. CJK). |
| theme | Theme | Customize fonts, colors, and spacing. |
| page | PageConfig | Page size and margins (default A4, 40pt margins). |
CLI
The package ships a md-2-ats binary:
npx md-2-ats cv.md # after publish; runs the default bin
md-2-ats cv.md # if installed (local or global)
md-2-ats cv.md -o out.pdf -p photo.png
md-2-ats cv.md --profile-position left --profile-size 80
md-2-ats cv.md --font-regular NotoSansSC-Regular.ttf| Flag | Description |
| -------------------------- | ------------------------------------------------ |
| -o, --output <path> | Output PDF path (default: input with .pdf). |
| -p, --profile <path> | Profile photo (PNG or JPEG). |
| --profile-size <points> | Profile photo side length (default 96). |
| --profile-position <pos> | left | center | right (default right). |
| --font-regular <path> | Custom TTF regular font. |
| --font-bold <path> | Custom TTF bold font. |
| --font-italic <path> | Custom TTF italic font. |
| -h, --help | Show help. |
| -v, --version | Show version. |
Profile Photo
A standalone image in its own paragraph renders as a top-right profile photo:
# Jane Doe

**Software Engineer** · JakartaSupply the photo bytes via the profile option; size (side length in
points, default 96) and position ("left" | "center" | "right", default
"right") are optional:
profile: {
bytes: await readFile("./profile.png"),
size: 96,
position: "right",
}Supported formats: PNG and JPEG. When no profile is provided, the image is skipped silently.
Custom Fonts
Standard fonts only cover Latin-1. To render CJK/Kana/Hangul, provide a TTF font (e.g. Noto Sans SC):
await generateCV({
input: "./cv.md",
output: "./cv.pdf",
fonts: {
regular: await readFile("./NotoSansSC-Regular.ttf"),
bold: await readFile("./NotoSansSC-Bold.ttf"), // optional
italic: await readFile("./NotoSansSC-Italic.ttf"), // optional
},
});bold/italic fall back to regular when omitted. Font files are your
responsibility (check their license). .ttc collections are not supported;
use a single .ttf/.otf.
Theming
import { createDefaultTheme } from "md-2-ats";
const theme = createDefaultTheme();
theme.text.body.family = "Times"; // "Helvetica" | "Times" | "Courier"
theme.text.name.size = 24;
theme.color.accent = { r: 0.1, g: 0.2, b: 0.5 };
theme.spacing.sectionGap = 8;
await generateCV({ input: "./cv.md", output: "./cv.pdf", theme });Validation
generateCV validates the document before rendering:
- Errors (throw): empty document, missing the level-1
# Nameheading. - Warnings (returned in
result.issues): missing contact/email, invalid email, heading-level skips, missing## Experience/## Education, empty entries, and unsupported Markdown.
const result = await generateCV({ input, output });
for (const issue of result.issues) console.warn(issue.message);Markdown Support
Supported blocks: headings (#–###), paragraphs, unordered/ordered lists
(including nested), blockquotes, thematic breaks (---), and inline formatting
(bold, italic, links, images, inline code).
Not rendered (reported as warnings): tables, code blocks, raw HTML.
Development
Clone the repo and install dependencies:
pnpm installRun the test suite and type-checking:
pnpm test
pnpm typecheckBuild the library:
pnpm buildRender the bundled example (example/cv.md → example/cv.pdf):
pnpm exampleLicense
This project is licensed under the MIT license.
Made with ☕ by Iqmal.
