---
title: "JSON Resume template for PDF and Word in JavaScript — quario"
description: "A ready-made quario resume that reads a JSON Resume file as it is. Experience, education and skills from one resume.json, rendered to PDF, Word, or HTML. Copy the definition and point it at your own resume.json."
url: "https://getquario.com/templates/json-resume"
---

# JSON Resume template for JavaScript

[JSON Resume](https://jsonresume.org) 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 title="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": "=@.institution != 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": "=@.position != 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": "=@.position != null",
      "style": { "color": "#5e6062" } },
    { "type": "text", "value": "{{ @.summary }}", "visible": "=@.position != null && @.summary != null",
      "style": { "color": "#1e1f22", "spaceBefore": 4 } },
    { "type": "text", "value": "{{ bullets(@.highlights) }}", "visible": "=@.position != null && @.highlights != null",
      "style": { "color": "#5e6062", "spaceBefore": 3 } },
    {
      "type": "split",
      "visible": "=@.institution != 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": "=@.institution != null",
      "style": { "color": "#5e6062" } },
    {
      "type": "split",
      "visible": "=@.keywords != 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:

```ts title="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](https://getquario.com/templates/payslip.md) 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:

```json title="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": "maren@visser.example",
    "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:

*Preview: the page renders this definition and its sample data live, with downloads — [see it rendered](https://getquario.com/templates/json-resume).*

## Use it

```bash title="shell"
npm install quario @quario/pdf @quario/docx
```

```ts title="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 →](https://getquario.com/docs/diving-deeper/security.md)

## 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 →](https://getquario.com/guides/docx-template.md)

### 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 →](https://getquario.com/pricing.md)

### Where are the other templates?

[Browse the template gallery →](https://getquario.com/templates.md) — certificates, report cards, invoices, and more.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://getquario.com/"},{"@type":"ListItem","position":2,"name":"Templates","item":"https://getquario.com/templates"},{"@type":"ListItem","position":3,"name":"JSON Resume","item":"https://getquario.com/templates/json-resume"}]}
```
