Escape

JSON Resume template for JavaScript

JSON Resume keeps a resume where a developer keeps everything else, in a file under version control. The content lives in resume.json, and a theme takes care of the layout. Most themes render it as a web page. A recruiter asks for a PDF, and an agency asks for Word. This is a ready-made quario resume that reads resume.json unchanged, with no conversion step. It renders the one file to PDF, Word, or HTML.

The template

The whole resume is a definition — data you can version beside the resume.json it reads. This is the exact file the preview below renders:

json-resume.report.json
{
  "data": "$['work','education','skills'][*]",
  "style": { "family": "sans", "size": 10 },
  "header": [
    { "type": "text", "value": "{{ $.input.basics.name }}",
      "style": { "family": "serif", "size": 28, "bold": true, "color": "#1e1f22" } },
    { "type": "text", "value": "{{ $.input.basics.label }}",
      "style": { "size": 12, "bold": false, "color": "#5e6062", "spaceBefore": 2 } },
    {
      "type": "text",
      "value": [
        { "value": "{{ $.input.basics.email }}", "style": { "href": "mailto:{{ $.input.basics.email }}" } },
        { "value": "  ·  {{ $.input.basics.phone }}  ·  " },
        { "value": "{{ $.input.basics.url }}", "style": { "href": "{{ $.input.basics.url }}" } },
        { "value": "  ·  {{ $.input.basics.location.city }}, {{ $.input.basics.location.countryCode }}" }
      ],
      "style": { "size": 8, "bold": false, "color": "#989a97", "spaceBefore": 10 }
    },
    { "type": "text", "value": "{{ $.input.basics.summary }}",
      "style": { "size": 10, "bold": false, "color": "#1e1f22", "spaceBefore": 18, "spaceAfter": 8 } }
  ],
  "groups": [
    {
      "name": "section",
      "by": "[email protected] != null ? 'Education' : @.keywords != null ? 'Skills' : 'Experience'",
      "header": [
        {
          "type": "split",
          "style": { "paddingTop": 22, "paddingBottom": 4,
            "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "{{ section.key }}", "width": 50,
              "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97" } },
            { "type": "text", "value": "", "style": { "size": 7 } }
          ]
        }
      ]
    }
  ],
  "detail": [
    {
      "type": "split",
      "visible": "[email protected] != null",
      "style": { "spaceBefore": 10 },
      "slots": [
        { "type": "text", "value": "{{ @.position }}", "width": 70,
          "style": { "size": 11, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ period(@.startDate) }} – {{ @.endDate != null ? period(@.endDate) : 'present' }}",
          "style": { "size": 8, "color": "#989a97", "align": "right" } }
      ]
    },
    { "type": "text", "value": "{{ @.name }}", "visible": "[email protected] != null",
      "style": { "color": "#5e6062" } },
    { "type": "text", "value": "{{ @.summary }}", "visible": "[email protected] != null && @.summary != null",
      "style": { "color": "#1e1f22", "spaceBefore": 4 } },
    { "type": "text", "value": "{{ bullets(@.highlights) }}", "visible": "[email protected] != null && @.highlights != null",
      "style": { "color": "#5e6062", "spaceBefore": 3 } },
    {
      "type": "split",
      "visible": "[email protected] != null",
      "style": { "spaceBefore": 10 },
      "slots": [
        { "type": "text", "value": "{{ @.studyType }}, {{ @.area }}", "width": 70,
          "style": { "size": 11, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ period(@.startDate) }} – {{ period(@.endDate) }}",
          "style": { "size": 8, "color": "#989a97", "align": "right" } }
      ]
    },
    { "type": "text", "value": "{{ @.institution }}", "visible": "[email protected] != null",
      "style": { "color": "#5e6062" } },
    {
      "type": "split",
      "visible": "[email protected] != null",
      "style": { "spaceBefore": 6 },
      "slots": [
        { "type": "text", "value": "{{ @.name }}", "width": 22,
          "style": { "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": "{{ join(@.keywords) }}",
          "style": { "color": "#5e6062" } }
      ]
    }
  ]
}

The rows come from three arrays at once. $['work','education','skills'][*] is a union, so the jobs come first, then the schools, then the skills, in the order the file lists them. The report header reads basics from $.input: the name, the label, the contact line, and the summary.

The sections are a group whose key looks at each row’s shape. A row with an institution is Education, a row with keywords is Skills, and the rest are Experience. Each kind of row has its own items in detail, and each item declares a visible test on the same fields. So a job prints as a job and a school as a school, from one stream of rows.

JSON Resume writes a date as a year and month, 2022-04. format: "date" reads a whole calendar date, so the definition calls period() instead, which prints April 2022. A job with no endDate reads present. The email and the website each sit in a styled run with an href, so they’re live links in the PDF.

The two helpers

A job’s highlights and a skill’s keywords are lists inside one row. Two small functions turn them into text, one as bulleted lines and one as a comma-separated line:

resume.funcs.ts
/** A list as one line, the way a skill's keywords read. */
export function join(list: string[] | undefined): string {
  return (list ?? []).join(", ");
}

/** A list as bulleted lines, the way a job's highlights read. */
export function bullets(list: string[] | undefined): string {
  return (list ?? []).map((item) => `•  ${item}`).join("\n");
}

period() is the month formatter the payslip registers too, and the snippet under Use it defines it.

The data

A definition renders nothing on its own — you hand it a document. This is an ordinary JSON Resume file, valid against the schema its first line names. quario needs nothing added to it:

resume.json
{
  "$schema": "https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json",
  "basics": {
    "name": "Maren Visser",
    "label": "Senior software engineer, documents and print",
    "email": "[email protected]",
    "phone": "+31 6 5550 1427",
    "url": "https://maren.visser.example",
    "summary": "Eight years building the systems that turn business data into documents people print, sign and file. Happiest where layout meets correctness: invoices that add up, statements that paginate, and PDFs that pass an archive check.",
    "location": { "city": "Amsterdam", "countryCode": "NL" },
    "profiles": [{ "network": "GitHub", "username": "mvisser", "url": "https://github.com/mvisser" }]
  },
  "work": [
    {
      "name": "Quire Press Ltd.",
      "position": "Lead engineer, digital production",
      "startDate": "2022-04",
      "summary": "Owns the pipeline from order data to print-ready files for a publisher of short-run editions.",
      "highlights": [
        "Replaced a headless-browser PDF service with declarative report definitions, cutting render time per catalogue from 40 s to 3 s",
        "Moved every customer document into version control, so a layout change is a reviewed diff",
        "Introduced PDF/A archiving for invoices and statements"
      ]
    },
    {
      "name": "Folio & Sons",
      "position": "Software engineer",
      "startDate": "2018-09",
      "endDate": "2022-03",
      "summary": "Built the ordering and fulfilment platform for a Dublin book distributor.",
      "highlights": [
        "Shipped packing slips, delivery notes and shipping labels generated straight from warehouse records",
        "Wrote the Excel exports finance still uses for month-end reconciliation"
      ]
    }
  ],
  "education": [
    {
      "institution": "University of Amsterdam",
      "area": "Computer Science",
      "studyType": "BSc",
      "startDate": "2014-09",
      "endDate": "2018-07"
    }
  ],
  "skills": [
    { "name": "Languages", "keywords": ["TypeScript", "Go", "SQL"] },
    { "name": "Documents", "keywords": ["PDF/A", "Typesetting", "Excel and Word exports"] },
    { "name": "Platform", "keywords": ["Node.js", "PostgreSQL", "AWS"] }
  ]
}

What it renders

This isn’t a screenshot. quario compiles the definition and the resume above, and @quario/viewer renders them in your browser, right here. The PDF button hands you the same resume as real @quario/pdf bytes, one A4 page with live links. The DOCX button beside it hands the same definition to @quario/docx. Word paginates it and resolves the faces itself, and the recipient can still edit it:

Rendering live with @quario/viewerA4

Use it

shell
npm install quario @quario/pdf @quario/docx
resume.ts
import { readFileSync, writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import { docx } from '@quario/docx'
import layout from './json-resume.report.json' with { type: 'json' }

// `bullets` and `join` are the functions from resume.funcs.ts
const month = new Intl.DateTimeFormat('en-GB', { month: 'long', year: 'numeric', timeZone: 'UTC' })
const period = (ym: string) => month.format(new Date(`${ym}-01`))

const report = quario({ locale: 'en-GB' }).report(layout, { bullets, join, period })
const resume = JSON.parse(readFileSync('resume.json', 'utf8'))

writeFileSync('resume.pdf', await report.render(pdf({ page: { size: 'A4', margin: 54 } }), resume))
writeFileSync('resume.docx', await report.render(docx({ page: { size: 'A4', margin: 54 } }), resume))

One report renders both files, from the one resume.json. For Letter paper, change size to 'letter' in both calls.

Make it yours

Because the resume is declarative, changing its look means changing data. Projects or certificates from the schema are one more name in the union and one more kind of row. A two-column layout puts the skills in a split beside the summary. A serif name and a sans body are one family each. One definition can serve every resume on a careers site, since each render is one data document. It renders without eval, so a resume uploaded by a stranger never becomes code. Security →

FAQ

Do I have to change my resume.json?

No. The definition reads the field names the JSON Resume schema already uses: basics, work, education and skills, with name, position, startDate, highlights and keywords inside them. The definition skips a field it doesn’t read, so a file with languages or projects still renders.

Can I render my resume to Word?

Yes, and recruitment agencies often ask for it. @quario/docx renders the same definition to a .docx that Word paginates itself, so the agency can add its own header without breaking the layout. Word documents →

Is it a JSON Resume theme?

No. A theme is a package the resume CLI calls to produce HTML. This is a quario definition you call from your own code. It gives you a PDF and a Word file as well as the page.

Can I use this resume template commercially?

The definition is yours — the report definitions, data, and output you create with quario belong to you. quario is free to evaluate with no time limit. Rendering resumes inside a product, such as a job board or a careers site, is past evaluation. That needs a license for each production deployment. Its users, agents, and renders are never counted. Pricing →

Where are the other templates?

Browse the template gallery → — certificates, report cards, invoices, and more.