JSON Resume schema: a complete example, and how to fill it from a CV
Published
A JSON Resume example is a JSON document with a fixed set of top-level sections — basics, work, volunteer, education, awards, certificates, publications, skills, languages, interests, references and projects — and nothing else is required. Below is a complete worked example of the whole document, then the part the standard's own pages leave open: how to get an existing PDF or DOCX CV into that shape. That last step is a mapping job, and this article does it concretely, including the field-by-field table from the output of a parsing API to the schema's field names.
One thing to say first, because this article is written by a vendor: ResumeJSON is a paid resume parsing API, and its output is its own schema, not JSON Resume's. If you want the exact JSON Resume shape you are mapping one object to another — a short, mechanical step, shown in full below. Every claim here about JSON Resume was read from jsonresume.org in September 2026, and the schema is theirs to change — the list of sections, the example below and the absence of any importer are all as of September 2026.
What the JSON Resume schema covers
JSON Resume describes itself as "a community driven open-source initiative to create JSON-based standard for resumes", released under the MIT license, with the schema on GitHub and a CLI, a registry and a gallery of community themes around it. The document is deliberately flat: one object, a fixed list of keys, arrays where a person can have several of something.
| Section | What goes in it | Where a CV states it |
|---|---|---|
basics | Name, label, email, phone, URL, summary, location, and profiles[] for GitHub, LinkedIn and similar | The header of the CV |
work[] | Employer name, position, url, startDate, endDate, summary, highlights[] | The experience section |
volunteer[] | Same shape as work, for unpaid roles | Usually a separate section, often omitted |
education[] | institution, area, studyType, dates, score, courses[] | The education section |
skills[] | Objects with name, level and keywords[] | A skills list or the text of the roles |
languages[] | language and fluency | Sometimes a sidebar, sometimes nowhere |
projects[] | name, description, highlights[], keywords[], roles[], dates | A portfolio section, or a GitHub profile |
awards[], certificates[], publications[] | One object each, with dates and an issuer or publisher | Separate sections, and the most often missing |
interests[], references[] | Names and keywords; reference letters | Almost never on a modern CV |
Two details matter more than the table suggests. Dates are free text in practice — the schema's own example shows "2013-01-01", but nothing enforces it, and a CV says "March 2022" or "2019 – Present", so normalising dates is work you own. And every section is optional: a valid JSON Resume can carry basics and one work entry, which is why the format is popular with tooling that would rather render nothing than crash.
A complete JSON Resume example
This is the whole document, written the way a filled-in one looks after a real CV has been mapped into it. It is abridged only in the number of entries per array.
{
"basics": {
"name": "John Doe",
"label": "Programmer",
"image": "",
"email": "john@gmail.com",
"phone": "(912) 555-4321",
"url": "https://johndoe.com",
"summary": "Backend engineer, eight years, mostly payments.",
"location": {
"address": "2712 Broadway St",
"postalCode": "CA 94115",
"city": "San Francisco",
"countryCode": "US",
"region": "California"
},
"profiles": [
{ "network": "Twitter", "username": "john", "url": "https://twitter.com/john" }
]
},
"work": [
{
"name": "Company",
"position": "President",
"url": "https://company.com",
"startDate": "2013-01-01",
"endDate": "2014-01-01",
"summary": "Description…",
"highlights": ["Started the company"],
"location": "San Francisco, CA"
}
],
"education": [
{
"institution": "University",
"url": "https://institution.com/",
"area": "Software Engineering",
"studyType": "Bachelor",
"startDate": "2011-01-01",
"endDate": "2013-01-01",
"score": "4.0",
"courses": ["DB1101 - Basic SQL"]
}
],
"skills": [
{ "name": "Web Development", "level": "Master", "keywords": ["HTML", "CSS", "JavaScript"] }
],
"languages": [{ "language": "English", "fluency": "Native speaker" }],
"projects": [
{
"name": "Project",
"description": "Description…",
"highlights": ["Won award at AIHacks 2016"],
"keywords": ["Time Tracking"],
"startDate": "2019-01-01",
"endDate": "2021-01-01",
"url": "https://project.com/",
"roles": ["Team Lead"],
"entity": "Entity",
"type": "application"
}
]
}work[] entries carry a name for the employer, not the person — the field is named for the company, which reads oddly the first time. basics.profiles[] is where GitHub, LinkedIn and personal sites go; there is no dedicated field for any of them.
Three ways to fill a JSON Resume from a CV
The standard ships no importer. jsonresume.org's own navigation is Home, Getting Started, CLI Tools, Schema, Themes, Projects, Hosting — there is no "upload a PDF" anywhere in it, so whoever fills the JSON is you. In practice there are three routes, and they differ mostly by how much of the CV you retype.
| Route | What you write | Honest verdict |
|---|---|---|
| Type it by hand | The JSON, in the CLI or the registry editor | Cheapest for one CV, unthinkable at fifty |
| Open-source extractor, then map | Extraction code plus your own field mapping | Good if CVs cannot leave your infrastructure, or parsing is not the point |
| Parsing API, then map | A parse call plus a field mapping | The mapping is the same work; the extraction is not |
Typing it by hand is genuinely fine and worth saying so. The JSON Resume CLI and the hosted registry exist for exactly this, and if you maintain one resume, a JSON file you edit twice a year beats a pipeline. It stops being fine when a form uploads a CV and someone has to retype it before the application can be scored.
An open-source extractor plus a mapping keeps everything on your own machines, which is the deciding factor if CVs cannot leave your infrastructure. The extraction half is solved — open source resume parser walks through what each project actually gives you, including where it stops. What that route costs you is the field decisions: turning a layout-preserving text rendering into a position and a startDate, and doing it again when your corpus shifts.
A parsing API plus a mapping moves the field decisions to somebody else and leaves you the mapping, which is a table lookup — the one below. The trade is a network call and a subscription instead of a corpus you maintain.
"JSON Resume is a community driven open-source initiative to create JSON-based standard for resumes." — jsonresume.org, read September 2026
Mapping a parsed resume into the schema
This is the whole mapping, from ResumeJSON's response object to JSON Resume's field names. Nothing else is needed: the output is typed, dates are already YYYY-MM or YYYY, and a field the CV does not state comes back null rather than guessed.
| JSON Resume | ResumeJSON output | Note |
|---|---|---|
basics.name | basics.full_name | |
basics.label | basics.headline | The CV's own wording, not a title you assign |
basics.email, basics.phone | basics.email, basics.phone | |
basics.location.address | basics.location | One string, so the parts stay joined |
basics.profiles[].url | basics.links[] | URLs only; the network is not classified |
basics.summary | — | Not extracted: a summary is prose you write |
work[].name | work[].company | |
work[].position | work[].title | |
work[].startDate, endDate | work[].start_date, end_date | Already normalised; end_date is null while a role is current, and work[].is_current carries that explicitly |
work[].highlights[] | work[].highlights[] | Bullet lines kept as an array |
education[].institution | education[].institution | |
education[].studyType, area | education[].degree, field_of_study | |
skills[].keywords[] | skills[] | An array of strings, so it maps onto keywords and leaves name and level to you |
languages[].language, fluency | languages[].language, proficiency | |
certificates[] | certifications[] | name, issuer and a YYYY-MM or YYYY date |
| — | total_years_experience | No JSON Resume field; computed from the roles, with overlapping roles counted once |
Two honest gaps. The schema has no place for a computed total, so total_years_experience is yours to use or drop. And basics.summary, skills[].level and the whole awards, publications and references families are not in a parse response — a parser reads what the document says, and levels or reference letters are usually not in it.
Who should not use a parsing API
The mapping above is small, so the reason to pick a route is not the mapping — it is where the extraction should live.
- Your CVs cannot leave your infrastructure. Then the answer is local extraction plus this mapping, at any cost in maintenance.
- You process a handful of resumes a year. Retype them. A pipeline you maintain for four documents is a hobby.
- The JSON Resume document is the source of truth and the CVs arrive already as JSON. Then you are validating a schema, not parsing paper; nothing here applies.
- You need the schema's
awards,publicationsorreferencessections filled from the document. No general parser reads those reliably, ours included, and a mapped empty array is worse than an honest missing key.
Everyone else — a job board, an ATS, a matching tool taking uploads from strangers — is choosing between maintaining field decisions or buying them, and the JSON Resume mapping is the same short table either way.
If you want to see the output shape before committing to any of this, the free parser returns the same JSON the paid endpoint does, and the field reference documents every key.