Resume to Excel: turn a folder of CVs into one spreadsheet
Published
To get resumes into Excel, you need one row per candidate and one column per fact: name, email, phone, current title, years of experience, skills. For five CVs, copy those values in by hand. For fifty or five hundred, run each file through a resume parser that returns structured fields, flatten the result into rows, and write a .xlsx or .csv that Excel opens directly. This article covers both routes, gives you a working script for the second, and says where each one stops being worth it.
It is written for the person holding the folder: a recruiter with a shared drive of applications, a developer asked to "just get these into a sheet" before a hiring meeting, a founder sorting a job board's first sign-ups. We build ResumeJSON, the parser the script below calls, so read this as written by an interested party. The manual route needs nothing from us.
Why a CV does not paste into Excel
A resume is a document laid out for a human eye. Opening a PDF and pasting its text into a sheet gives you one long column of lines in reading order, sometimes out of order when the CV uses two columns, with dates written five different ways and skills run together in a paragraph. Excel's own import tools work on data that is already a table, so they cannot help here.
What you actually want is a small, fixed set of columns. A useful default:
| Column | Where it comes from | Watch out for |
|---|---|---|
| Full name | The header of the CV | Name written in capitals, or split across two lines |
| Contact block | Several addresses on one CV | |
| Phone | Contact block | Country code present on some, missing on others |
| Location | Contact block or latest role | "Remote" is a location on some CVs |
| Current title | Most recent role | The CV's headline is often a different claim from the job title |
| Current company | Most recent role | Freelancers list several at once |
| Years of experience | Counted from the work history | Overlapping roles counted twice by hand |
| Skills | Skills section, plus the roles | One cell, separated by a semicolon |
| Source file | Your folder | Keep it, so every row leads back to the document |
Decide the columns before you start. Adding a column after 200 rows means reopening 200 files.
The manual way: when it is enough
For a handful of CVs, typing is the right tool. Open the CV beside the sheet, fill one row, move on. A few habits make the result usable later:
- Write dates as
YYYY-MM. "March 2022", "03/22" and "Mar '22" sort as text in three different places;2022-03sorts correctly as plain text. - Put the file name in the last column. When somebody asks where a number came from, the answer is one click away.
- Keep skills in one cell, separated by
;. Excel's Text to Columns or aFILTERwithSEARCHcan split or query them later, and the row stays one row per person. - Count experience from the roles, not from the summary line. "10+ years" in a headline is the candidate's claim. The dates are the evidence.
The manual way is enough when you have fewer than about twenty CVs and will not repeat the job. Past that, typing becomes the slow part of the hiring process, and the errors start: a transposed digit in a phone number, a skill left off because it sat in a role description instead of the skills list.
The faster way: parse, flatten, write the sheet
The automated version has three steps, and only the first needs anything you do not already have.
- Parse each file into structured data. A resume parser reads the PDF, DOCX or scanned image and returns the fields as JSON.
- Flatten that JSON into one row. Nested lists like the work history become a few chosen columns.
- Write the rows to a spreadsheet file.
ResumeJSON returns a resume object with basics (including full_name, email, phone, location, headline), a work array with company, title, start_date, end_date and is_current on each entry, an education array, a skills array of strings, and total_years_experience, which is computed from the work history rather than read off the CV. Dates come back as YYYY-MM, YYYY or null, so habit 1 above is already done for you. Every field is listed in the field reference.
The script
This is Python with two dependencies, requests and openpyxl. Put your CVs in a folder called cvs, set your key, and run it.
import pathlib, requests
from openpyxl import Workbook
URL = "https://resumejson-resume-cv-parser-api.p.rapidapi.com/v1/parse"
HEADERS = {
"x-rapidapi-key": "YOUR_KEY",
"x-rapidapi-host": "resumejson-resume-cv-parser-api.p.rapidapi.com",
}
COLUMNS = ["Full name", "Email", "Phone", "Location", "Current title",
"Current company", "Years of experience", "Skills", "Source file"]
def to_row(resume, source):
basics = resume["basics"]
work = resume["work"]
latest = next((w for w in work if w["is_current"]), work[0] if work else {})
return [
basics["full_name"], basics["email"], basics["phone"], basics["location"],
latest.get("title"), latest.get("company"),
resume["total_years_experience"],
"; ".join(resume["skills"]),
source,
]
wb = Workbook()
ok, failed = wb.active, wb.create_sheet("Failed")
ok.title = "Candidates"
ok.append(COLUMNS)
failed.append(["Source file", "Status", "Reason"])
for path in sorted(pathlib.Path("cvs").iterdir()):
with path.open("rb") as f:
resp = requests.post(URL, headers=HEADERS, files={"file": f}, timeout=60)
if resp.ok:
ok.append(to_row(resp.json()["resume"], path.name))
else:
body = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {}
reason = body.get("error", {}).get("code") or body.get("message") or resp.text[:200]
failed.append([path.name, resp.status_code, reason])
wb.save("candidates.xlsx")Open candidates.xlsx and you have a Candidates sheet with one row per CV and a Failed sheet listing every file that did not parse, with the reason. Keep the failure sheet. A file that comes back not_a_resume or unreadable_file is usually a cover letter or a corrupt upload, and you want to see it rather than lose a candidate silently.
If you would rather have a CSV, replace the workbook with Python's csv module and write the same rows; Excel opens a UTF-8 CSV fine, though names with accents survive more reliably in .xlsx.
Choosing which role counts as "current"
The script takes the first role marked is_current, and falls back to the first role listed. That is a reasonable default for a shortlist. Two cases deserve a second look:
- Several current roles. A consultant or a part-time founder may have two. If that matters for your search, write both titles into the cell, joined by
;. - No current role. Somebody between jobs has every role closed. The fallback puts their most recent listed role in the column, which is what a recruiter usually wants to see.
Scans, photos and other formats
A folder of real applications is never all clean PDFs. The parser detects the type from the file's own bytes, so a .pdf that is really a scan still works: a PDF without a text layer, or a JPEG, PNG or WebP photo of the page, is read as an image instead. DOCX and plain text are accepted too. Uploads go up to 20 MB each.
If your archive is mostly scans, OCR resume parser covers what changes when the CV is an image.
What it costs, and how long it takes
As of September 2026, our published plans on RapidAPI are:
| Plan | Monthly price | Parses included | Past the quota |
|---|---|---|---|
| Basic | $0 | 100 | Hard cap, never bills |
| Pro | $0 | None, pay per use | $0.05 a parse |
| Ultra | $29 | 1,000 | $0.045 a parse |
| Mega | $99 | 5,000 | $0.018 a parse |
A folder of up to 100 CVs a month fits the free Basic plan. A one-off folder of 400 costs $20 on pay per use. A typical parse takes about two seconds, so the script above, calling one file at a time, gets through a few hundred CVs while you make coffee. For thousands, run a few requests at once and keep a status table so a crash does not restart from zero; Bulk resume parsing walks through that.
Every call is metered whatever the answer, including a refused file, so drop duplicates and non-CV attachments from the folder before you run it.
Before you share the sheet
A spreadsheet of candidates is personal data about real people, and it travels further than a folder does. Two habits help:
- Share the sheet, not the folder. The sheet holds what the hiring team needs; the CVs stay where access is already controlled.
- Delete the sheet when the role closes, or at the point your own retention policy says. The source file column lets you find everything again if you need to.
ResumeJSON parses the document and writes nothing down, so the only copy of the result is the one in your spreadsheet. Our data handling page has the detail.
Try one CV first
Before you write the loop, see what comes back for a real CV from your folder. The free resume parser takes one file with no signup and shows the JSON the script would receive, so you can check that the columns you planned are the ones the document actually fills. If you are building something bigger than a spreadsheet, such as a screening step or a candidate database, Automated resume screening and How to build an ATS pick up where this article stops.