Packing slip template for JavaScript
A packing slip travels inside the box. It says what the box holds and nothing about what it cost. The warehouse on the other end can check the contents against it without seeing a price. The awkward part is that a shipment is several boxes. The person unpacking carton two wants to know what’s in carton two, not a flat list of the whole order. This is a ready-made quario packing slip — copy it, point it at your shipment, and render to HTML, PDF, Excel, or Word.
The template
The whole slip is a definition — data you can version and review. The addresses, the carrier line, the per-carton sections and the discrepancy notice are all in here. Nothing about this document lives in code. This is the exact file the preview below renders:
{
"data": "$.shipment.lines[*]",
"sort": [{ "by": "[email protected]" }],
"aggregates": {
"cartons": "countDistinct:[email protected]",
"lines": "count",
"units": "sum:[email protected]"
},
"style": { "family": "sans", "size": 10 },
"header": [
{
"type": "split",
"slots": [
{ "type": "text", "value": "Packing slip No. {{ $.input.shipment.number }}", "width": 60,
"style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
{ "type": "text", "value": [{ "value": "Shipped " }, { "value": "{{ $.input.shipment.shipped }}" }],
"style": { "size": 8, "color": "#989a97", "align": "right", "format": "date" } }
]
},
{
"type": "split",
"style": { "spaceAfter": 4 },
"slots": [
{ "type": "text", "value": "Order {{ $.input.shipment.orderRef }}", "width": 60,
"style": { "size": 8, "color": "#989a97" } },
{ "type": "text", "value": "{{ $.input.shipment.carrier }} · {{ $.input.shipment.tracking }}",
"style": { "size": 8, "color": "#989a97", "align": "right" } }
]
},
{
"type": "split",
"style": { "spaceBefore": 34 },
"slots": [
{ "type": "text", "value": "Ship from", "width": 50,
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
{ "type": "text", "value": "Deliver to",
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } }
]
},
{
"type": "split",
"slots": [
{ "type": "text", "value": "{{ $.input.shipment.from.name }}", "width": 50,
"style": { "bold": true, "color": "#1e1f22" } },
{ "type": "text", "value": "{{ $.input.shipment.to.name }}",
"style": { "bold": true, "color": "#1e1f22" } }
]
},
{
"type": "split",
"style": { "spaceAfter": 30 },
"slots": [
{ "type": "text", "value": "{{ $.input.shipment.from.street }}\n{{ $.input.shipment.from.city }}\n{{ $.input.shipment.from.country }}", "width": 50,
"style": { "color": "#5e6062" } },
{ "type": "text", "value": "{{ $.input.shipment.to.street }}\n{{ $.input.shipment.to.city }}\n{{ $.input.shipment.to.country }}",
"style": { "color": "#5e6062" } }
]
}
],
"groups": [
{
"name": "carton",
"by": "[email protected]",
"aggregates": { "lines": "count", "units": "sum:[email protected]" },
"header": [
{
"type": "split",
"style": { "spaceBefore": 14, "spaceAfter": 2 },
"slots": [
{ "type": "text", "value": "Carton {{ carton.key }} of {{ $.cartons }}", "width": 50,
"style": { "size": 11, "bold": true, "color": "#1e1f22" } },
{ "type": "text", "value": "{{ carton.lines }} {{ carton.lines == 1 ? 'line' : 'lines' }} · {{ carton.units }} units",
"style": { "size": 8, "color": "#989a97", "align": "right" } }
]
}
]
}
],
"detail": {
"header": {
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
"borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
},
"row": { "style": { "color": "#5e6062" } },
"columns": [
{ "header": "SKU", "value": "{{ @.sku }}", "width": 16, "style": { "family": "mono", "size": 8 } },
{ "header": "Item", "value": "{{ @.item }}", "width": 60 },
{ "header": { "value": "Qty", "style": { "align": "right" } }, "value": "{{ @.qty }}", "width": 12, "style": { "align": "right", "color": "#1e1f22" } },
{ "header": "Unit", "value": "{{ @.unit }}", "width": 12, "style": { "color": "#989a97" } }
]
},
"footer": [
{
"type": "split",
"style": { "spaceBefore": 24, "paddingTop": 6,
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
"slots": [
{ "type": "text", "value": "{{ $.cartons }} cartons · {{ $.lines }} lines · {{ $.units }} units", "width": 60,
"style": { "bold": true, "color": "#1e1f22" } },
{ "type": "text", "value": "Gross {{ $.input.shipment.grossKg }} kg",
"style": { "color": "#5e6062", "align": "right" } }
]
},
{
"type": "split",
"style": { "spaceBefore": 10 },
"slots": [
{ "type": "text", "value": "Packed by", "width": 22,
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
{ "type": "text", "value": "{{ $.input.shipment.packedBy }}",
"style": { "size": 8, "color": "#5e6062" } }
]
},
{
"type": "split",
"visible": "=$.input.shipment.notes != null",
"slots": [
{ "type": "text", "value": "Notes", "width": 22,
"style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
{ "type": "text", "value": "{{ $.input.shipment.notes }}",
"style": { "size": 8, "color": "#5e6062" } }
]
}
],
"page": {
"footer": [
{
"type": "text",
"value": "Contents only — no prices. Report a shortfall or damage to {{ $.input.shipment.from.email }} within 5 days, quoting {{ $.input.shipment.number }}.",
"style": { "size": 8, "color": "#989a97", "paddingTop": 10,
"borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" }
}
]
}
}The cartons are a group. "by": "[email protected]" opens one section per distinct carton number in
the rows, and each section carries its own aggregates. carton.lines and carton.units cover
that carton’s rows alone. The group header reads them beside carton.key, the carton number
itself, and beside the report-level $.cartons, a countDistinct over the whole shipment.
That’s how the line reads Carton 2 of 3. The report sort on the carton field is what puts
the sections in order. A group opens where its key first appears.
One declaration styles the column labels, on detail.header, and another the data rows, on
detail.row. Since quario 0.5.0 a row’s style resolves onto its cells on every target. The
rule under the labels and the mono face on them is one declaration rather than one per column. A
column states only what differs — the right alignment on Qty, the mono face on the SKU.
There is no format: "currency" anywhere in this file, which is the point of the document. The
totals in the footer are counts: cartons, lines and units, each a report aggregate the footer reads
from $. The Notes line declares "visible": "=$.input.shipment.notes != null", so a shipment
without a note has no empty label.
The data
A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. $.shipment.lines[*] in the definition selects its rows, each naming its carton, and
$.input reads the rest:
{
"shipment": {
"number": "PS-2026-0311",
"shipped": "2026-08-27",
"orderRef": "SO 2026-1042",
"carrier": "PostNL Cargo",
"tracking": "3SQPX 4471 0082 19",
"grossKg": 41.6,
"packedBy": "T. Meijer",
"notes": "Carton 3 contains the proof copies; open first.",
"from": {
"name": "Quire Press Ltd.",
"street": "Binnenkant 42",
"city": "1011 BM Amsterdam",
"country": "Netherlands",
"email": "[email protected]"
},
"to": {
"name": "Folio & Sons",
"street": "12 Marlborough Street",
"city": "Dublin 1",
"country": "Ireland"
},
"lines": [
{ "carton": 1, "sku": "CB-QC-250", "item": "Case-bound copies, quarter cloth, 1–120", "qty": 120, "unit": "copies" },
{ "carton": 2, "sku": "CB-QC-250", "item": "Case-bound copies, quarter cloth, 121–240", "qty": 120, "unit": "copies" },
{ "carton": 3, "sku": "CB-QC-250", "item": "Case-bound copies, quarter cloth, 241–250", "qty": 10, "unit": "copies" },
{ "carton": 3, "sku": "PR-A4", "item": "Bound proof copies", "qty": 2, "unit": "copies" },
{ "carton": 3, "sku": "EP-MRB", "item": "Spare marbled endpapers", "qty": 20, "unit": "sheets" },
{ "carton": 3, "sku": "DJ-250", "item": "Loose dust jackets", "qty": 30, "unit": "pcs" }
]
}
}The rows are flat, one line per SKU per carton, with the carton number on the row. That’s the shape a warehouse system already has, and the group does the nesting on the page.
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 slip as real @quario/pdf
bytes, one A4 page, no browser rendering involved. 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 packingSlip from './packing-slip.report.json' with { type: 'json' }
const report = quario({ locale: 'en-IE' }).report(packingSlip)
const data = await getShipment(id) // your warehouse data
const bytes = await report.render(pdf({ page: { size: 'A4', margin: 54 } }), data)
writeFileSync('packing-slip.pdf', bytes)The template hands quario() no currency, because nothing on the slip is money. The date on the
header line — a styled run of its own beside the Shipped label — still presents through the
locale, 27 Aug 2026 under en-IE.
See JSON to PDF for both PDF routes side by side.
Make it yours
Because the slip is declarative, changing it means changing data. One carton per page for a big
consignment is "break": "page" on the group. A weight per carton is a field on the row and a
sum in the group’s aggregates. A barcode line is a slot that reads the tracking number in the
mono face. Drop the group entirely and the same definition prints a single flat list. All of it
diffs in a pull request, and the same file renders identically in Node, the browser, and at the
edge. It renders without eval, so product names from your catalogue never become code.
Security →
FAQ
Can I generate the packing slip as a PDF?
Yes, and it’s the format that goes in the box. @quario/pdf renders this definition straight to
bytes with no browser anywhere, so a dispatch job can print it as the shipment closes. Full
example →
Can I export the packing slip to Excel?
Yes — @quario/xlsx renders the same definition to a workbook. Each carton lands as its own
section with its header row. The quantities are real numbers the receiving side can sum
against their goods-received count. Full example →
How’s this different from the delivery note?
A packing slip lists what’s in the boxes and stays with them. A delivery note is the document the recipient signs: it compares the order with what arrived and carries the outstanding balance. Same rows, different obligation, so the gallery keeps two definitions.
Can I use this packing slip 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. Shipping real orders is past evaluation, so that needs a seat for each developer working with it. Deployment is then unlimited and royalty-free. Pricing →
Where are the other templates?
Browse the template gallery → — delivery notes, purchase orders, invoices, and more.