Certificate template for JavaScript
A certificate is mostly white space and a name. What makes it worth generating rather than hand-typing is the part under the name. The modules covered, the hours, the result, a number somebody can verify. This is a ready-made quario certificate of completion — copy it, point it at your records, and render to HTML, PDF, or Word. It’s laid out on landscape A4, and the paper is the host’s choice, not the document’s.
The template
The whole certificate is a definition — data you can version and review. The issuer line, the headline, the holder’s name, the module table, the signature, and the verification line are all in here. Nothing about this document lives in code. This is the exact file the preview below renders:
{
"data": "$.certificate.modules[*]",
"aggregates": {
"hours": "sum:[email protected]",
"modules": "count"
},
"style": { "family": "serif", "size": 11 },
"header": [
{ "type": "text", "value": "{{ $.input.certificate.issuer.name }}",
"style": { "family": "mono", "size": 8, "bold": false, "uppercase": true, "color": "#989a97", "align": "center", "spaceAfter": 20 } },
{ "type": "text", "value": "Certificate of Completion",
"style": { "size": 32, "bold": true, "color": "#1e1f22", "align": "center" } },
{ "type": "text", "value": "This certifies that",
"style": { "family": "sans", "size": 10, "bold": false, "color": "#5e6062", "align": "center", "spaceBefore": 18 } },
{ "type": "text", "value": "{{ $.input.certificate.holder.name }}",
"style": { "size": 24, "bold": false, "italic": true, "color": "#1e1f22", "align": "center" } },
{ "type": "text", "value": [{ "value": "has completed " }, { "value": "{{ $.input.certificate.course }}", "style": { "bold": true, "color": "#1e1f22" } }],
"style": { "family": "sans", "size": 10, "bold": false, "color": "#5e6062", "align": "center", "spaceBefore": 6 } },
{ "type": "text", "value": [{ "value": "{{ $.input.certificate.from }}" }, { "value": " to " }, { "value": "{{ $.input.certificate.to }}" }],
"style": { "family": "sans", "size": 10, "bold": false, "color": "#5e6062", "align": "center", "format": "date" } },
{ "type": "text", "value": "{{ $.hours }} hours of instruction across {{ $.modules }} modules",
"style": { "family": "sans", "size": 10, "bold": false, "color": "#5e6062", "align": "center", "spaceAfter": 22 } }
],
"detail": {
"header": {
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
"borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
},
"row": { "style": { "family": "sans", "size": 9, "color": "#5e6062" } },
"columns": [
{ "header": "Module", "value": "{{ @.name }}", "width": 60 },
{ "header": { "value": "Hours", "style": { "align": "right" } }, "value": "{{ @.hours }}", "width": 15, "style": { "align": "right" } },
{ "header": { "value": "Result", "style": { "align": "right" } }, "value": "{{ @.result }}", "width": 25, "style": { "align": "right", "color": "#1e1f22" } }
],
"total": {
"style": { "family": "sans", "size": 9, "bold": true, "color": "#1e1f22", "align": "right", "paddingTop": 4,
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
"rows": [
{
"cells": [
{ "value": "Overall", "style": { "align": "left" } },
{ "value": "{{ $.hours }}" },
{ "value": "{{ $.input.certificate.result }}" }
]
}
]
}
},
"footer": [
{
"type": "split",
"style": { "spaceBefore": 40 },
"slots": [
{ "type": "text", "value": "{{ $.input.certificate.signatory.name }}\n{{ $.input.certificate.signatory.role }}", "width": 32,
"style": { "family": "sans", "size": 8, "color": "#5e6062", "paddingTop": 6,
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" } },
{ "type": "text", "value": "", "width": 36 },
{ "type": "text", "value": [{ "value": "Certificate No. {{ $.input.certificate.number }}\nIssued " }, { "value": "{{ $.input.certificate.issued }}" }],
"style": { "family": "sans", "size": 8, "color": "#5e6062", "align": "right", "paddingTop": 6, "format": "date",
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" } }
]
}
],
"page": {
"footer": [
{ "type": "text", "value": "Verify this certificate at {{ $.input.certificate.issuer.verify }}",
"style": { "family": "mono", "size": 7, "color": "#989a97", "align": "center" } }
]
}
}The document sets itself in serif at the top, so the headline and the name inherit the face and
only state their size. The report header is where the size matters. That band carries a
headline default — bold, 14 point — above the document’s own style, so every item in it states
its own size. The lines that shouldn’t be bold say "bold": false outright. The module table
and the footer have no such default and simply say sans where they want it.
The rows are the modules. $.hours is a sum over them and $.modules a count. The line 32
hours across 4 modules comes from the same rows the table lists, and the Overall row reads the
same sum. The certificate can’t claim hours the table doesn’t show. The course dates are three
styled runs — a date, the word to, a date. The line’s "format": "date" presents each as 8 Jun 2026 to 21 Aug 2026 under en-IE. The hours line is a separate item, so the kind leaves the
numbers in it alone. The course name is a bold run inside its own sentence, and that weight reaches
the PDF and the workbook, not only the HTML.
The signature and the certificate number are one split. Each slot wears a borderTop with the
text under it, and an empty slot between them holds the gap. A slot that renders nothing keeps its
width. The verification line is page.footer, so it sits on the paper’s baseline whatever the
header above it runs to.
The data
A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. $.certificate.modules[*] in the definition selects its rows, and $.input reads the
holder, the course, the signatory and the number:
{
"certificate": {
"number": "QPA-2026-0142",
"issued": "2026-08-28",
"course": "Bookbinding Foundations",
"from": "2026-06-08",
"to": "2026-08-21",
"result": "Pass with distinction",
"holder": { "name": "Ada Brekelmans" },
"issuer": { "name": "Quire Press Academy", "verify": "academy.quirepress.example/verify/QPA-2026-0142" },
"signatory": { "name": "M. Duyvendak", "role": "Head of studio" },
"modules": [
{ "name": "Paper grain, folding and gathering", "hours": 6, "result": "Pass" },
{ "name": "Sewing structures", "hours": 8, "result": "Distinction" },
{ "name": "Case binding, quarter cloth", "hours": 12, "result": "Distinction" },
{ "name": "Finishing and foil stamping", "hours": 6, "result": "Pass" }
]
}
}What it renders
This isn’t a screenshot. quario compiles the definition and data above, and @quario/viewer renders
them in your browser. That’s landscape A4, because it’s what this page’s host asked for. The PDF
button hands you the same certificate as real @quario/pdf bytes, one page, 841.89 × 595.28 points.
The definition asks for serif, sans and mono. The faces you see are this page’s own, handed to
the viewer and the PDF export as the same TrueType files. The DOCX button beside it hands the same
definition to @quario/docx, where Word paginates and resolves the faces itself:
Use it
npm install quario @quario/pdfThe paper is a render option, not part of the document. size takes a [width, height] in points,
and landscape A4 is portrait A4 turned round:
import { writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import certificate from './certificate.report.json' with { type: 'json' }
const report = quario({ locale: 'en-IE' }).report(certificate)
const data = await getCertificate(id) // your records
const bytes = await report.render(pdf({ page: { size: [841.89, 595.28], margin: 64 } }), data)
writeFileSync('certificate.pdf', bytes)Hand pdf() a fonts record and the certificate takes your own faces — the preview above
does exactly that. See the PDF target for embedding fonts.
Make it yours
Because the certificate is declarative, changing it means changing data. A logo is an image item
whose source yields the bytes. A grade band is a visible expression on a line that only prints
for a distinction. A certificate of attendance drops the module table and keeps the header. A
batch is one definition and one data document per holder — the same file renders identically in
Node, the browser, and at the edge. It renders without eval, so a holder’s own name never
becomes code. Security →
FAQ
Can I generate the certificate as a PDF?
Yes, and it’s the format you send: @quario/pdf renders this definition straight to bytes, with
the faces you hand it embedded, and no browser anywhere. Full example →
Does it render to Excel?
It can — @quario/xlsx renders any definition — but a certificate is a page, not a grid. The
module table lands as real cells. The headline and the signature lines land as text rows above and
below it. Use the workbook for the register of who holds a certificate, not for the certificate
itself.
Word is the exception among the non-PDF targets. A certificate is a page, and @quario/docx is a
page-shaped target, so the landscape geometry and the signature lines survive. The recipient can
also sign or amend it. Word documents →
Can I use this certificate 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. Issuing certificates to real people is past evaluation, so that needs a seat for each developer working with it. Deployment is then unlimited and royalty-free. Pricing →
Can one definition cover every holder?
Yes. The definition is the certificate. Each render is one data document. A second holder is another JSON file, not another report.
Where are the other templates?
Browse the template gallery → — summary reports, payslips, invoices, and more.