Work order template for JavaScript
A work order follows a job from the call to the sign-off. It says what broke, on which machine, what the technician did about it, and what that took in hours and parts. The customer signs it on site, and the invoice comes from it afterwards. A field-service app produces one for every visit. This is a ready-made quario work order — copy it, point it at your job data, and render to PDF, Word, Excel, or HTML.
The template
The whole work order is a definition — data you can version and review. The provider line, the dates, the equipment and fault block, the labour and parts, the totals and the signatures are all in here. This is the exact file the preview below renders:
{
"data": "$.workOrder.lines[*]",
"aggregates": {
"subtotal": "sum:[email protected] * @.unitPrice",
"hours": "sum:[email protected] == 'h' ? @.qty : 0"
},
"style": { "family": "sans", "size": 10 },
"header": [
{
"type": "split",
"slots": [
{ "type": "text", "value": "{{ $.input.workOrder.provider.name }}", "width": 50,
"style": { "size": 10, "color": "#1e1f22" } },
{ "type": "text", "value": "{{ $.input.workOrder.provider.street }}, {{ $.input.workOrder.provider.city }}",
"style": { "size": 8, "bold": false, "color": "#989a97", "align": "right" } }
]
},
{
"type": "split",
"style": { "spaceBefore": 28 },
"slots": [
{ "type": "text", "value": "Work order No. {{ $.input.workOrder.number }}", "width": 60,
"style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
{ "type": "text", "value": "{{ $.input.workOrder.priority }} priority",
"style": { "size": 8, "bold": true, "uppercase": true, "color": "#1e1f22", "align": "right" } }
]
},
{
"type": "grid",
"columns": [{ "width": 25 }, { "width": 25 }, { "width": 25 }, {}],
"rows": [
{
"style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97", "paddingTop": 24 },
"slots": [
{ "type": "text", "value": "Customer" },
{ "type": "text", "value": "Requested" },
{ "type": "text", "value": "Scheduled" },
{ "type": "text", "value": "Completed" }
]
},
{
"style": { "size": 10, "bold": false, "color": "#1e1f22" },
"slots": [
{ "type": "text", "value": "{{ $.input.workOrder.customer.name }}", "style": { "bold": true } },
{ "type": "text", "value": "{{ $.input.workOrder.requested }}", "style": { "format": "date" } },
{ "type": "text", "value": "{{ $.input.workOrder.scheduled }}", "style": { "format": "date" } },
{ "type": "text", "value": "{{ $.input.workOrder.completed }}", "style": { "format": "date" } }
]
},
{
"style": { "size": 10, "bold": false, "color": "#5e6062" },
"slots": [
{ "type": "text", "value": "{{ $.input.workOrder.customer.street }}\n{{ $.input.workOrder.customer.city }}\nAttn. {{ $.input.workOrder.customer.contact }}" },
{ "type": "text", "value": "" },
{ "type": "text", "value": "" },
{ "type": "text", "value": "" }
]
}
]
},
{
"type": "grid",
"columns": [{ "width": 22, "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } }, { "style": { "color": "#1e1f22" } }],
"rows": [
{
"style": { "size": 10, "bold": false, "paddingTop": 24, "paddingBottom": 3 },
"slots": [{ "type": "text", "value": "Equipment" }, { "type": "text", "value": "{{ $.input.workOrder.asset.name }}, serial {{ $.input.workOrder.asset.serial }}" }]
},
{
"style": { "size": 10, "bold": false, "paddingBottom": 3 },
"slots": [{ "type": "text", "value": "Location" }, { "type": "text", "value": "{{ $.input.workOrder.asset.location }}" }]
},
{
"style": { "size": 10, "bold": false, "paddingBottom": 3 },
"slots": [{ "type": "text", "value": "Reported fault" }, { "type": "text", "value": "{{ $.input.workOrder.problem }}" }]
},
{
"style": { "size": 10, "bold": false, "paddingBottom": 24 },
"slots": [{ "type": "text", "value": "Work done" }, { "type": "text", "value": "{{ $.input.workOrder.resolution }}" }]
}
]
}
],
"groups": [
{
"name": "kind",
"by": "[email protected]",
"aggregates": { "amount": "sum:[email protected] * @.unitPrice" },
"header": [
{ "type": "text", "value": "{{ kind.key }}",
"style": { "size": 11, "bold": true, "color": "#1e1f22", "spaceBefore": 10, "spaceAfter": 2 } }
],
"footer": [
{
"type": "split",
"style": { "paddingTop": 5, "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#dcdcdc" },
"slots": [
{ "type": "text", "value": "{{ kind.key }} subtotal", "width": 82,
"style": { "size": 8, "color": "#989a97", "align": "right" } },
{ "type": "text", "value": "{{ round(kind.amount, 2) }}",
"style": { "color": "#1e1f22", "align": "right", "format": "currency" } }
]
}
]
}
],
"detail": {
"header": {
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
"borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
},
"row": { "style": { "color": "#5e6062" } },
"columns": [
{ "header": "Ref.", "value": "{{ @.ref }}", "width": 14, "style": { "family": "mono", "size": 8 } },
{ "header": "Description", "value": "{{ @.description }}", "width": 40, "style": { "color": "#1e1f22" } },
{ "header": { "value": "Qty", "style": { "align": "right" } }, "value": "{{ @.qty }}", "width": 8, "style": { "align": "right" } },
{ "header": "Unit", "value": "{{ @.unit }}", "width": 8, "style": { "color": "#989a97" } },
{ "header": { "value": "Rate", "style": { "align": "right" } }, "value": "{{ @.unitPrice }}", "width": 12,
"style": { "align": "right", "format": "currency" } },
{ "header": { "value": "Amount", "style": { "align": "right" } }, "value": "{{ round(@.qty * @.unitPrice, 2) }}", "width": 18,
"style": { "align": "right", "color": "#1e1f22", "format": "currency" } }
]
},
"footer": [
{
"type": "grid",
"columns": [{ "width": 82, "style": { "align": "right" } }, { "style": { "align": "right", "format": "currency" } }],
"rows": [
{
"style": { "color": "#5e6062", "paddingTop": 14 },
"slots": [{ "type": "text", "value": "Subtotal, {{ $.hours }} hours on site" }, { "type": "text", "value": "{{ round($.subtotal, 2) }}" }]
},
{
"style": { "color": "#5e6062" },
"slots": [{ "type": "text", "value": "VAT 21%" }, { "type": "text", "value": "{{ round($.subtotal * 0.21, 2) }}" }]
},
{
"style": { "size": 11, "bold": true, "color": "#1e1f22", "paddingTop": 6, "paddingBottom": 4,
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
"slots": [{ "type": "text", "value": "Total" }, { "type": "text", "value": "{{ round(round($.subtotal, 2) + round($.subtotal * 0.21, 2), 2) }}" }]
}
]
},
{
"type": "grid",
"columns": [{ "width": 45 }, { "width": 10 }, {}],
"rows": [
{
"style": { "paddingTop": 56 },
"slots": [
{ "type": "text", "value": "", "style": { "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" } },
{ "type": "text", "value": "" },
{ "type": "text", "value": "", "style": { "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" } }
]
},
{
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97", "paddingTop": 4 },
"slots": [
{ "type": "text", "value": "Technician · {{ $.input.workOrder.technician }}" },
{ "type": "text", "value": "" },
{ "type": "text", "value": "Accepted for the customer" }
]
}
]
}
],
"page": {
"footer": [
{
"type": "text",
"value": "The customer's signature confirms the work above was carried out and the equipment left in working order. Invoiced separately; questions to {{ $.input.workOrder.provider.email }}.",
"style": { "size": 8, "color": "#989a97", "paddingTop": 10,
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" }
}
]
}
}Labour and parts are a group on [email protected], so each gets its own heading and its own subtotal in
the group footer. One table definition serves both, and a line’s unit says whether it’s hours,
pieces, or litres. The hours on site in the footer are one aggregate,
sum:[email protected] == 'h' ? @.qty : 0.
Every amount follows the rule that a currency value is a whole number of cents. A line is
round(@.qty * @.unitPrice, 2), and the subtotals and the VAT round where they print.
The total adds the two rounded figures and rounds once more. So the workbook holds €458.83, not a
float that only looks like it.
The equipment, the fault and the work done sit on a grid with the label column stated once, in the mono face. A long fault description wraps inside its own column and never under its label.
The data
A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. $.workOrder.lines[*] in the definition selects its rows, and $.input reads the job:
{
"workOrder": {
"number": "WO-2026-0187",
"requested": "2026-08-31",
"scheduled": "2026-09-03",
"completed": "2026-09-03",
"priority": "High",
"provider": {
"name": "Platen Service B.V.",
"street": "Distelweg 88",
"city": "1031 HH Amsterdam",
"email": "[email protected]"
},
"customer": {
"name": "Quire Press Ltd.",
"street": "Binnenkant 42",
"city": "1011 BM Amsterdam",
"contact": "T. Meijer"
},
"asset": { "name": "Heidelberg platen press, 10 × 15 in", "serial": "T 104 557", "location": "Press room, ground floor" },
"problem": "Feeder misses every few sheets at speed; uneven impression on the left of the forme.",
"resolution": "Replaced worn suction cups and the feeder valve gasket. Reset platen dwell and levelled the platen to 0.02 mm. Ran 500 test sheets with no misfeeds.",
"technician": "R. Bakker",
"lines": [
{ "kind": "Labour", "ref": "LAB-STD", "description": "Diagnosis and strip-down of feeder", "qty": 1, "unit": "h", "unitPrice": 72 },
{ "kind": "Labour", "ref": "LAB-STD", "description": "Platen levelling and dwell reset", "qty": 2, "unit": "h", "unitPrice": 72 },
{ "kind": "Labour", "ref": "LAB-TRV", "description": "Call-out, Amsterdam zone", "qty": 1, "unit": "visit", "unitPrice": 45 },
{ "kind": "Parts", "ref": "HD-SC-12", "description": "Suction cup, rubber, 12 mm", "qty": 8, "unit": "pcs", "unitPrice": 4.35 },
{ "kind": "Parts", "ref": "HD-VG-03", "description": "Feeder valve gasket", "qty": 1, "unit": "pcs", "unitPrice": 23.9 },
{ "kind": "Parts", "ref": "OIL-68", "description": "Press oil, ISO VG 68, 1 L", "qty": 2, "unit": "L", "unitPrice": 11.75 }
]
}
}What it renders
This isn’t a screenshot. quario compiles the definition and data above, and @quario/viewer renders
them in your browser, right here. The PDF button hands you the same work order as real
@quario/pdf bytes, one A4 page with its terms on the baseline. 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/pdfimport { writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import workOrder from './work-order.report.json' with { type: 'json' }
const q = quario({ locale: 'en-IE', currency: 'EUR' }) // what `format` presents through
const report = q.report(workOrder)
const data = await getWorkOrder(id) // your job data
const bytes = await report.render(pdf({ page: { size: 'A4', margin: 54 } }), data)
writeFileSync('work-order.pdf', bytes)Locale and currency belong to the instance, not the definition. A provider working in pounds hands
quario() a different currency, and the work order doesn’t change.
Make it yours
Because the work order is declarative, changing it means changing data. A checklist of safety
checks is a third value of kind with no rate. A job with no charge to the customer, under a
service contract, hides the totals with visible. A photo of the fault is an image item that
reads PNG bytes from your job record. The same definition renders an unsigned copy before the visit
and the signed one after it. It renders without eval, so a fault typed in by a customer never
becomes code. Security →
FAQ
How’s this different from an invoice?
A work order records the job. It says what was wrong, what the technician did, and what it took, and the customer signs it on site. The invoice comes later and asks for payment, usually for several work orders at once. The lines are the same data, so you can build the invoice from the work orders.
Can I export the work order to Excel?
Yes — @quario/xlsx renders the same definition to a workbook. Labour and parts land as their own
sections with their subtotals, and every amount is a real number a back office can reconcile. Full
example →
Can I use this work order 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 work orders for real jobs is past evaluation, so 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 → — timesheets, invoices, quotes, and more.