Guide

The Markdown Resume Guide: Writing and Exporting an ATS-Friendly PDF

Published July 30, 2026

A resume is a short document with unusually high stakes and two very different readers: a human who spends about ten seconds on the first pass, and a parser that reduces it to structured fields. Optimising for one at the expense of the other is the most common way resumes fail.

Markdown suits this well. It keeps content in plain text you can version and diff, it produces clean semantic HTML, and — crucially — the PDF it exports contains real text rather than a flattened image, which is the single most important property for machine parsing.

Why write a resume in plain text

You can version it. A resume evolves over years across dozens of applications. In Git you have a history: what you sent, when, and what changed. Recovering the version you sent to a company three months ago is a git log away.

Content and presentation separate. The same Markdown produces different PDFs by swapping CSS. Change the typeface across the whole document by editing one line, not by selecting text in a word processor and hoping the styling took.

Diffs are meaningful. git diff on a Word document tells you nothing. On Markdown it shows exactly which bullet you rewrote.

No formatting drift. Word documents accumulate invisible state — a stray style override, a manual space that pushes a line. Plain text has none of that. What you see is what is there.

It is fast to edit. Tailoring a resume for a specific role means rewording bullets, not fighting a layout. Keyboard only, no mouse.

The trade-off is real: you give up drag-and-drop visual design. For a resume, which should be typographically conservative anyway, that is close to no loss.

How ATS parsing actually works

Worth understanding properly, because most advice about it is folklore.

An applicant tracking system ingests your PDF and attempts to extract structured fields: name, contact details, work history with employers and date ranges, education, skills. Modern parsers — the ones behind Workday, Greenhouse, Lever, iCIMS — do this by reading the PDF’s text layer and applying heuristics about layout and section labelling.

What follows from that:

A text-layer PDF is essential. If your PDF is an image — exported from a design tool as a raster, or scanned — the parser gets nothing without OCR, and OCR results are poor. A browser-generated PDF from HTML always contains real text. You can verify by opening the PDF and trying to select a word: if you can select it, the parser can read it.

Reading order matters more than visual position. The PDF text layer has an order, which comes from the DOM order in an HTML-based export. If your CSS visually reorders content — a sidebar that appears left but sits later in the DOM — the parser reads the DOM order, not what you see. This is the main reason multi-column resume templates parse badly.

Tables are risky for structure. Parsers handle simple tables inconsistently. A two-column table used to lay out “dates on the left, role on the right” may be read column-first, producing all your dates followed by all your job titles. Use normal block flow for anything structural.

Standard section headings help. Parsers look for recognisable labels. “Experience”, “Work Experience”, “Education”, “Skills” are matched reliably; “Where I’ve Made an Impact” is not. Be conventional in headings and creative in content.

Headers and footers are often skipped. Some parsers discard page margin content as boilerplate. Never put your contact details in a running header — put them in the body, at the top.

Graphics carry no information. Skill-rating bars, icons, and progress rings convey nothing to a parser. If a bar chart says you are “expert” in Python, write “Python” in a skills list too. The visual can stay; it just cannot be the only representation.

Two myths worth dispelling: parsers do not penalise you for using a PDF (nearly all handle PDF fine — it is images they cannot read), and there is no evidence that a specific font choice affects parsing, provided the text is real text.

Structure

The order below is conventional and parses reliably. Deviating has a cost; deviate deliberately.

# Jordan Rivera

**Senior Backend Engineer**

Berlin, Germany · jordan.rivera@example.com · +49 30 1234 5678
[linkedin.com/in/jordanrivera](https://linkedin.com/in/jordanrivera) · [github.com/jrivera](https://github.com/jrivera)

## Summary

Backend engineer with eight years building payment infrastructure at scale.
Led the migration of a monolithic ledger to event-sourced services handling
€4B annual volume. Looking for staff-level work on systems where correctness
matters more than velocity.

## Experience

### Senior Backend Engineer — Fintech GmbH
*March 2021 – present · Berlin*

- Redesigned the settlement pipeline from nightly batch to streaming,
  cutting reconciliation lag from 18 hours to under 4 minutes.
- Led a four-engineer team through the PSD2 compliance programme; delivered
  on schedule with zero audit findings.
- Introduced property-based testing to the ledger core, surfacing 11 latent
  rounding defects before they reached production.
- Reduced p99 API latency from 840ms to 120ms by replacing per-request
  currency lookups with a versioned in-process cache.

### Backend Engineer — Commerce Platform AG
*July 2018 – February 2021 · Munich*

- Built the idempotency layer for the public payments API, eliminating
  duplicate-charge incidents entirely (previously ~30 per quarter).
- Migrated 140 services from Python 2 to 3 across nine months as part of a
  two-person platform team.

## Education

### MSc Computer Science — Technical University of Munich
*2016 – 2018 · Distributed Systems specialisation*

### BSc Software Engineering — University of Porto
*2013 – 2016*

## Skills

**Languages:** Go, Python, Rust, SQL, TypeScript
**Infrastructure:** Kubernetes, Terraform, PostgreSQL, Kafka, Redis
**Practices:** Event sourcing, property-based testing, incident command

Notes on that structure

h1 is your name, once. It is the document title. Not “Resume” — that is what the file is, not what it says.

Job titles as h3, under an h2 section. This gives the parser a clear hierarchy: section → position. Putting the company in the same heading as the title works well; parsers generally split on the dash.

Dates in italics on their own line. Not in a table, not floated. Use an unambiguous format — “March 2021 – present”, not “3/21 –”. Parsers handle written months more reliably than numeric ones, and 2021–2023 with an en dash is clearer than 2021-2023.

Bullets start with a verb and contain a number. This is writing advice rather than formatting advice, but it is the difference between a resume that reads as a job description and one that reads as a record of impact. “Redesigned the settlement pipeline, cutting lag from 18 hours to 4 minutes” tells the reader what changed. “Responsible for settlement pipeline” does not.

Skills as labelled lines, not a table. Bold label plus comma-separated values parses cleanly and reads quickly.

Link text should be the URL. [linkedin.com/in/jordanrivera](https://...) rather than [LinkedIn](https://...). If the resume is printed, the reader can still type it.

What to leave out

Photographs (illegal to consider in many jurisdictions, and a parser artefact), date of birth, marital status, full postal address — city and country is enough — references, and “References available on request”, which conveys nothing. Skip an objective statement in favour of a summary that says what you have done rather than what you want.

Layout CSS

A resume has one hard constraint most documents do not: it should fit on one page, or exactly two, with nothing stranded on a third.

@page {
  size: A4;
  margin: 14mm 16mm;
}

@media print {
  body {
    font-family: "Inter", "Helvetica Neue", Arial, sans-serif;
    font-size: 10pt;
    line-height: 1.42;
    color: #1a1a1a;
  }

  /* Name */
  h1 {
    font-size: 21pt;
    font-weight: 700;
    letter-spacing: -0.02em;
    margin: 0 0 1pt;
  }

  /* Current title, immediately under the name */
  h1 + p strong {
    font-size: 11.5pt;
    font-weight: 600;
    color: #333;
  }

  /* Contact line */
  h1 + p + p {
    font-size: 9pt;
    color: #555;
    margin-top: 3pt;
  }

  /* Section headings — small caps rule across the page */
  h2 {
    font-size: 9.5pt;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: #333;
    border-bottom: 0.75pt solid #d4d4d8;
    padding-bottom: 2pt;
    margin: 13pt 0 6pt;
    break-after: avoid;
    page-break-after: avoid;
  }

  /* Role — company */
  h3 {
    font-size: 10.5pt;
    font-weight: 650;
    margin: 8pt 0 0;
    break-after: avoid;
    page-break-after: avoid;
  }

  /* Dates and location */
  h3 + p em {
    font-size: 9pt;
    font-style: normal;
    color: #666;
  }

  h3 + p {
    margin: 1pt 0 3pt;
  }

  ul {
    margin: 3pt 0 0;
    padding-left: 13pt;
  }

  li {
    margin: 2pt 0;
    padding-left: 1pt;
  }

  /* Keep a role's heading, dates and bullets together. */
  h3, h3 + p, h3 + p + ul {
    break-inside: avoid;
    page-break-inside: avoid;
  }

  a {
    color: #1d4ed8;
    text-decoration: none;
  }

  /* Do not expand link URLs — the resume already shows them. */
  a[href]::after { content: none; }
}

The last rule matters if you are reusing a general print stylesheet that appends URLs after links. On a resume that produces linkedin.com/in/jordanrivera (https://linkedin.com/in/jordanrivera), which is absurd.

A two-column header, safely

A name on the left with contact details right-aligned looks good and is safe specifically because it is a small, atomic unit that will always fit on one page — the exception discussed in the print CSS guide.

<div class="resume-head">

# Jordan Rivera

<div class="contact">
Berlin, Germany<br>
jordan.rivera@example.com<br>
+49 30 1234 5678
</div>

</div>
.resume-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  break-inside: avoid;
}

.resume-head .contact {
  font-size: 9pt;
  color: #555;
  text-align: right;
  line-height: 1.5;
}

Note the DOM order: name first, then contact. The parser reads them in that order, which is correct. If you flipped the visual arrangement with flex-direction: row-reverse, the visual would change but the reading order would stay correct — that is the safe way to reorder.

Do not use this pattern for the body of the resume. A sidebar column containing skills and education, with experience in a main column, is the classic ATS-hostile layout: reading order becomes ambiguous and content gets interleaved.

Fitting one page

When you are 15 lines over, work through these in order. They are ranked by how much space they buy per unit of ugliness.

  1. Cut the oldest job to one line, or drop it. Nobody is evaluating your 2013 internship. A “Earlier: Junior Developer at X (2013–2015)” line preserves the timeline in one line.
  2. Cut weak bullets. Four strong bullets beat seven mediocre ones. Anything without a specific outcome is a candidate.
  3. Tighten wording. “Was responsible for leading the team that developed” → “Led development of”. Resume prose should be compressed to the edge of terseness.
  4. Reduce line-height to 1.35. Below about 1.3 it becomes visibly cramped.
  5. Reduce margins to 12mm. Below 10mm risks clipping on physical printers.
  6. Drop body text to 9.5pt. Below 9pt is a readability problem for anyone over forty, which includes a lot of hiring managers.
  7. Tighten section spacing. h2 { margin-top: 10pt } instead of 13pt.
  8. Two-column the skills list. Skills are a list of short items, which is exactly what columns suit:
.skills-grid {
  column-count: 2;
  column-gap: 12mm;
}

Resist compressing everything at once. A resume crammed to 8.5pt with 8mm margins signals desperation more than it demonstrates experience. If you genuinely have fifteen years of relevant work, two well-composed pages are more credible than one illegible page — and two pages is entirely normal above about ten years’ experience.

Checking the page count reliably

The live preview in a paginated editor shows this directly. If you are using a CLI pipeline, check programmatically rather than eyeballing:

pdfinfo resume.pdf | grep Pages

Add it to a pre-commit hook if you revise often.

Tailoring without duplicating

Applying to fifteen roles means fifteen slightly different resumes. Copying the file fifteen times guarantees they diverge and you lose track of which is current.

Branch per application. Keep main as your canonical resume and branch for each application:

git checkout -b apply/acme-staff-backend
# edit the summary and reorder bullets
git commit -am "Tailor for Acme staff backend role"

The branch name records where it went, and git log main..apply/acme-staff-backend shows exactly what you changed. Improvements that are generally good get merged back to main.

Or one source with variant sections. Keep everything in one file and mark role-specific content:

<div class="only-backend">

- Redesigned the settlement pipeline from nightly batch to streaming.

</div>

<div class="only-leadership">

- Grew the payments team from 4 to 11 engineers over two years.

</div>
/* Toggle by swapping which line is commented. */
.only-leadership { display: none; }
/* .only-backend { display: none; } */

Less clean, but everything stays in one file. Suits people who apply to two or three distinct kinds of role rather than many individual companies.

Keep a master list of bullets. A separate file with every accomplishment you might ever mention, from which each resume selects. Tailoring becomes choosing, not writing.

Filenames and delivery

Small things that get noticed:

Name the file properly. Jordan-Rivera-Resume.pdf, not resume-final-v3-ACTUALLY-FINAL.pdf. Recruiters have folders full of files called resume.pdf.

Set the PDF title metadata. Many viewers show the document title rather than the filename in the tab. In an HTML-based export this comes from <title>.

Check the file size. Under 500KB is comfortable; some portals reject above 2MB. If a text-only resume is 3MB, an unsubsetted font is embedded — see the typography guide.

Verify the text layer before sending. Open the PDF, select all, copy, paste into a text editor. What you see is approximately what the parser sees. If it is empty, garbled, or badly ordered, fix that before anything else.

That last check takes fifteen seconds and catches the failure mode that actually costs people interviews.

A complete template

Copy this into an editor and replace the content:

# Your Name

**Your Current Title**

City, Country · you@example.com · +00 000 000 0000
[linkedin.com/in/yourname](https://linkedin.com/in/yourname) · [github.com/yourname](https://github.com/yourname)

## Summary

Two or three sentences: what you do, the scale you have done it at, one
concrete signature achievement, and what you are looking for next.

## Experience

### Job Title — Company Name
*Month Year – Month Year · City*

- Verb-first bullet with a specific, quantified outcome.
- Verb-first bullet naming scope: team size, system scale, budget.
- Verb-first bullet describing something you changed and its effect.

### Previous Job Title — Previous Company
*Month Year – Month Year · City*

- Two or three bullets. Older roles get fewer.

## Education

### Degree — Institution
*Year – Year*

## Skills

**Languages:**
**Tools:**
**Practices:**

Try it yourself

The editor here has a resume example loaded and ready — open it, replace the content, and the paginated preview shows exactly where the page break falls as you type, which is the thing that makes one-page fitting quick rather than iterative. Export produces selectable vector text, so the ATS text layer is correct by construction, and nothing is uploaded anywhere, which matters for a document full of your personal contact details.

There is also a dedicated Markdown resume to PDF page with the tool and a worked example.

About this guide

Written and maintained by the developer of MarkdownToFile — the browser-based converter this site runs on. Techniques described here are tested against the engines named in the text (Chromium's print pipeline, Paged.js, WeasyPrint), and engine limitations are stated rather than glossed over. Corrections are welcome via the contact page; more about the project on the about page.

Try it yourself — free, no signup

Convert your Markdown to a polished PDF right in your browser.

Open the editor