What an ATS reads from your PDF

The same resume, shown twice: the page as it prints, and the literal text a parser pulls out of it, in order. Four ways that text comes out wrong while the page looks perfect.

Last reviewed

Almost every piece of resume advice about applicant tracking systems is written from the outside — someone guessing at what a parser does, then recommending a safe-looking layout. This page is written from the inside. We render resumes to PDF with headless Chromium and then read the text back out of our own files, on every template, in an automated test. What follows is that output.

The mechanism, in one paragraph

A PDF does not contain paragraphs. It contains instructions to draw glyphs at coordinates. Extracting text means walking those instructions in the order the file lists them and reassembling words from position and spacing. So the question “is my resume ATS-friendly?” is really two questions: is the text there at all, and does it come out in the order a human would read it. A page can look immaculate and fail both.

What our own extraction returns

Below is the literal first page of a two-page sample resume, rendered through the classic template and extracted with pdfjs. Line breaks are the extractor’s, not ours.

Priya Raghunathan
Senior Backend Engineer
[email protected] | +1 (415) 555-0142 | San Francisco, CA, USA | linkedin.com/in/priyaraghunathan | github.com/praghunathan
Summary
Backend engineer with eight years building payment and identity systems at scale. Led the migration of a monolithic billing service
to event-driven microservices serving 40M requests a day.
Experience
Northwind Payments Mar 2022 - Present
Senior Backend Engineer San Francisco, CA
Led the decomposition of a 400k-line billing monolith into eight event-driven services, cutting median deploy time from 45
minutes to under 4.
Designed an idempotent ledger write path that eliminated double-charge incidents entirely across 40M daily requests.
...
Education
Indian Institute of Technology Madras Jul 2013 - May 2017
B.Tech, Computer Science and Engineering 8.7/10 CGPA
Skills
Languages: Go, TypeScript, Python, SQL, Java

Three things in that output are worth pointing at, because they are what a parser downstream has to cope with.

  • Section headings arrive as bare lines. Summary, Experience, Education, Skills — no markup, no styling, nothing that says “heading”. A parser recognises them because they are the words it expects, alone on a line, in the order it expects. That is the entire reason conventional section names beat clever ones: rename Experience to “Where I’ve Made An Impact” and the text is still all there, and the machine no longer knows what it is looking at.
  • An employer and its dates come out on one line. Northwind Payments Mar 2022 - Present is two visually separated things — left-aligned name, right-aligned dates — collapsed into a single line by position. That is normal and parsers handle it. What they do not handle is the same trick performed across a table cell boundary.
  • Bullets lose their markers. The bullet glyph is drawn by the list, not stored as a character, so each bullet is simply its own line. This is why pasting “•” into your bullet text is counterproductive: the template already draws one, and the pasted one becomes literal text sitting beside it.

Four ways the text comes out wrong while the page looks perfect

Each of these was found by running the extraction, not by reading a specification. Each one is now prevented in code, and each one is invisible on screen.

1. A hand-built bullet marker reorders your whole resume

A very common way to style a bullet is position: relative on the list item with an absolutely positioned ::before holding the dot. It looks identical to a real list. It also moves that list item into the positioned paint layer — and Chromium writes PDF text in paint order. The result: every bullet in the document extracts after every employer name, so the parser sees a block of job titles followed by an unattributed pile of achievements. Nothing on screen changes. Use list-style and ::marker.

2. One CSS property deletes every digit

font-variant-numeric: tabular-nums is the correct choice typographically — it lines up columns of figures. Under some variable fonts, including Inter, it renders perfectly and extracts with the digits missing entirely. Dates, percentages, team sizes: gone. A resume whose quantified achievements are its strongest feature arrives with the quantities removed, and the author has no way to see it.

3. Wide letter-spacing splits headings one character at a time

Tracked-out uppercase headings are a design signature of most “modern” templates. Past roughly 0.08em the extractor stops recognising the glyph run as a word and emits E X P E R I E N C E — which no section-name matcher will ever match. And the ceiling is not a guarantee: bold uppercase Merriweather split into one run per character at 0.08em, so a new typeface has to be measured rather than assumed.

4. Adding a photo changes the layout enough to lose a line

A portrait in the header introduces a flex row, and a flex row is a layout change. The first time we ran the extraction test with a photo present, one template sized its name block to max-content and pushed the last contact link off the page — the candidate’s GitHub URL, silently absent from the file and from the parse.

The one artifact we cannot fix, and will not hide

In the same dump, the certification line comes out as Certi fi ed Kubernetes Administrator. That is the fi ligature: the font draws one glyph for two letters, and the extractor reconstructs it with spaces around it. Different parsers differ here — many map the ligature back through the font’s character map and get Certified — but the honest statement is that some do not, and that no visual inspection of the PDF would ever reveal it. It is the clearest example of the point this page exists to make: you cannot proofread a parse by looking at the page.

What this means for how you write a resume

  1. Use conventional section names. Summary, Experience, Education, Skills, Projects, Certifications. This is the single highest leverage choice available and it costs nothing.
  2. Keep the reading order canonical. Summary → Experience → Education → Skills is what parsers are calibrated against. A layout that visually reorders that is fine; one that reorders the underlying text is not.
  3. Do not paste bullet characters, tabs or manual columns. Let the template draw structure. Every character you add to fake layout is a character the parser has to read as content.
  4. Send a text PDF, never a scan or an image. An exported image has no text layer at all: the extraction is empty and the score is zero, regardless of how good the resume is.
  5. Check the extraction, not the appearance. That is the whole reason the free checker below reports what it read rather than just a number.

Related

  • What actually breaks resume parsing: a controlled experiment

    We took 33 resume templates, changed one CSS declaration at a time, and extracted the text back out of every render. Which formatting choices destroy text, which silently reorder it, and which turn out to be harmless.

  • Are two-column resumes ATS-safe?

    Mostly yes, with one real cost nobody mentions. What decides whether a side column extracts in the right order, and what a column does to a two-page resume.

  • Fonts that break resume parsing

    A font can embed correctly, print perfectly, and still lose every digit in your dates. Three measured failures, including one that cost us a shipped typeface.

  • PDF or Word: which should you send?

    PDF, unless the posting says otherwise — and the reason is not the one usually given. What each format actually guarantees about the text a parser receives.

  • Turning your LinkedIn profile into a resume

    LinkedIn will hand you a PDF of your own profile. What that file actually contains, the three things in it that are the wrong shape for a resume, and what to fix first after importing it.