---
title: "Shipping label template (4×6) for JavaScript — quario"
description: "A ready-made quario shipping label for JavaScript. A 4×6 in label per parcel, with a Code 128 barcode of the tracking number. Copy the definition and the barcode function, point them at your shipment, and render to PDF or HTML."
url: "https://getquario.com/templates/shipping-label"
---

# Shipping label template for JavaScript

A shipping label is the one document a parcel can’t travel without. It’s 4 by 6 inches, the size a
thermal label printer takes. The address has to read from across a sorting hall, and a scanner has
to read the barcode under it. A shipment of three parcels needs three labels, each saying which of
the three it is. This is a ready-made quario shipping label — copy it, point it at your shipment,
and render one label per parcel to PDF or HTML.

## The template

The whole label is a definition — data you can version and review. The sender block, the service
band, the delivery address, the parcel line and the barcode are all in here. This is the exact file
the preview below renders:

```json title="shipping-label.report.json"
{
  "data": "$.shipment.parcels[*]",
  "sort": [{ "by": "=@.number" }],
  "aggregates": { "parcels": "count" },
  "style": { "family": "sans", "size": 9 },
  "groups": [{ "name": "parcel", "by": "=@.number", "break": "between" }],
  "detail": [
    {
      "type": "split",
      "style": { "paddingBottom": 8, "borderBottomWidth": 1.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" },
      "slots": [
        { "type": "text", "value": [{ "value": "From\n", "style": { "family": "mono", "size": 6, "uppercase": true, "color": "#989a97" } }, { "value": "{{ $.input.shipment.from.name }}\n{{ $.input.shipment.from.street }}\n{{ $.input.shipment.from.city }}\n{{ $.input.shipment.from.country }}" }], "width": 62,
          "style": { "size": 7, "color": "#5e6062" } },
        { "type": "text", "value": "{{ $.input.shipment.service }}",
          "style": { "size": 15, "bold": true, "uppercase": true, "align": "center", "valign": "middle",
            "color": "#ffffff", "background": "#1e1f22", "paddingTop": 6, "paddingBottom": 6 } }
      ]
    },
    { "type": "text", "value": "Ship to",
      "style": { "family": "mono", "size": 6, "uppercase": true, "color": "#989a97", "spaceBefore": 12 } },
    { "type": "text", "value": "{{ $.input.shipment.to.name }}",
      "style": { "size": 16, "bold": true, "color": "#1e1f22", "spaceBefore": 2 } },
    { "type": "text", "value": "Attn. {{ $.input.shipment.to.attn }}",
      "visible": "=$.input.shipment.to.attn != null",
      "style": { "size": 10, "color": "#5e6062" } },
    { "type": "text", "value": "{{ $.input.shipment.to.street }}\n{{ $.input.shipment.to.city }}",
      "style": { "size": 12, "color": "#1e1f22", "spaceBefore": 4 } },
    { "type": "text", "value": "{{ $.input.shipment.to.country }}",
      "style": { "size": 12, "bold": true, "uppercase": true, "color": "#1e1f22" } },
    { "type": "text", "value": "Tel. {{ $.input.shipment.to.phone }}",
      "visible": "=$.input.shipment.to.phone != null",
      "style": { "size": 8, "color": "#5e6062", "spaceBefore": 4, "spaceAfter": 12 } },
    {
      "type": "grid",
      "columns": [
        { "width": 30, "style": { "paddingTop": 6 } },
        { "width": 30, "style": { "paddingTop": 6 } },
        { "style": { "paddingTop": 6, "align": "right" } }
      ],
      "rows": [
        {
          "style": { "family": "mono", "size": 6, "uppercase": true, "color": "#989a97",
            "borderTopWidth": 1.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "Parcel" },
            { "type": "text", "value": "Weight" },
            { "type": "text", "value": "Shipped" }
          ]
        },
        {
          "style": { "size": 13, "bold": true, "color": "#1e1f22", "paddingBottom": 8 },
          "slots": [
            { "type": "text", "value": "{{ @.number }} of {{ $.parcels }}", "style": { "paddingTop": 0 } },
            { "type": "text", "value": "{{ @.kg }} kg", "style": { "paddingTop": 0 } },
            { "type": "text", "value": "{{ $.input.shipment.shipped }}", "style": { "paddingTop": 0, "format": "date" } }
          ]
        }
      ]
    },
    { "type": "image", "source": "=barcode(@.tracking)", "fit": "width", "alt": "Barcode for tracking number {{ @.tracking }}",
      "style": { "spaceBefore": 4, "paddingTop": 10, "borderTopWidth": 1.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" } },
    { "type": "text", "value": "{{ @.tracking }}",
      "style": { "family": "mono", "size": 10, "color": "#1e1f22", "align": "center", "spaceBefore": 4 } },
    { "type": "text", "value": "{{ $.input.shipment.carrier }} · Order {{ $.input.shipment.orderRef }}",
      "style": { "size": 7, "color": "#989a97", "align": "center", "spaceBefore": 8, "spaceAfter": 40 } }
  ]
}
```

The rows are the parcels, `$.shipment.parcels[*]`, and the shipment itself is `$.input`. One label
per parcel is a **group** on the parcel number with `"break": "between"`. Every boundary between two
parcels turns a page, and the first label starts on the first page. The aggregate `$.parcels` counts
the rows, so each label reads _1 of 3_, _2 of 3_ and _3 of 3_ without the data saying how many there
are.

`detail` is an array of items rather than a table, because a label isn’t a grid and a table cell
can’t hold an image. The barcode is an `image` item whose `source` is `=barcode(@.tracking)`. That
row’s tracking number goes in, and the bars come back as SVG bytes. `"fit": "width"` scales them to
the label’s full width, which is what a scanner wants.

The service band is one text slot with a dark `background` and white type. Each rule between the
blocks is a border on a split, a grid row, or the barcode itself. Nothing on the label needs a
coordinate.

## The barcode

A Code 128 barcode, the symbology carriers print for a tracking number. [bwip-js](https://github.com/metafloor/bwip-js)
draws it as SVG, synchronously, and it runs in Node and in the browser. A barcode library that needs
`await` would have to draw before the render and pass the bytes in, because an image `source` can’t
wait:

```ts title="barcode.ts"
import bwipjs from "bwip-js";

/** A Code 128 barcode of `text`, returned as the SVG's bytes. */
export function barcode(text: string): Uint8Array {
  const svg = bwipjs.toSVG({ bcid: "code128", text, height: 20 });
  // bwip-js sizes the drawing by `viewBox` alone; the image item needs a width and height to scale from.
  const [, , width, height] = svg.match(/viewBox="([^"]+)"/)![1].split(" ");
  return new TextEncoder().encode(
    svg.replace("<svg ", `<svg width="${width}" height="${height}" `),
  );
}
```

## The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview
below uses. Each parcel carries its own number, tracking number and weight, and everything the
labels share sits once on the shipment:

```json title="shipping-label.data.json"
{
  "shipment": {
    "orderRef": "SO 2026-1042",
    "shipped": "2026-08-27",
    "service": "Express",
    "carrier": "PostNL Cargo",
    "from": {
      "name": "Quire Press Ltd.",
      "street": "Binnenkant 42",
      "city": "1011 BM Amsterdam",
      "country": "Netherlands"
    },
    "to": {
      "name": "Folio & Sons",
      "attn": "Goods inwards",
      "street": "12 Marlborough Street",
      "city": "Dublin 1, D01 T2X0",
      "country": "Ireland",
      "phone": "+353 1 555 0142"
    },
    "parcels": [
      { "number": 1, "tracking": "3SQPX4471008219", "kg": 14.2 },
      { "number": 2, "tracking": "3SQPX4471008220", "kg": 14.2 },
      { "number": 3, "tracking": "3SQPX4471008221", "kg": 13.2 }
    ]
  }
}
```

## What it renders

This isn’t a screenshot. quario compiles the definition and data above, runs `barcode.ts` in your
browser for each parcel, and `@quario/viewer` paints three labels right here. The PDF button hands
you the same labels as real `@quario/pdf` bytes, three pages of 288 × 432 points. The bars are
vector paths, which a label printer prints sharp. There’s no Word button: `@quario/docx` refuses an SVG
image.

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

## Use it

```bash title="shell"
npm install quario @quario/pdf bwip-js
```

The label’s size is a render option, not part of the document. `size` takes a `[width, height]` in
points, and 4 × 6 inches is 288 × 432:

```ts title="shipping-label.ts"
import { writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import label from './shipping-label.report.json' with { type: 'json' }

// `barcode` is the function from barcode.ts
const report = quario({ locale: 'en-IE' }).report(label, { barcode })

const data = await getShipment(id)                   // your warehouse data
const bytes = await report.render(pdf({ page: { size: [288, 432], margin: 14 } }), data)

writeFileSync('labels.pdf', bytes)                   // one page per parcel
```

The shipped date presents through the instance’s locale, `27 Aug 2026` under `en-IE`. Nothing on
the label is money, so the instance needs no currency.

## Make it yours

Because the label is declarative, changing it means changing data. A label on A6 paper is a
different `size` in the render call, and the definition doesn’t move. A return label is the sender
and the recipient swapped in the data. A routing code above the barcode is one more text item in a
large mono face. A QR code in place of the bars is a different `bcid` in the function, as on the
[event ticket](https://getquario.com/templates/event-ticket.md). It renders **without `eval`**, so an address typed into
your checkout never becomes code. [Security →](https://getquario.com/docs/diving-deeper/security.md)

## FAQ

### Will a carrier accept this label?

The layout is yours to match what your carrier asks for. Carriers that accept your own labels usually
want the tracking number as a Code 128 barcode. They also want a clear delivery address and the
parcel’s weight. Check the carrier’s label specification for the minimum height and quiet zone of
the bars, and set `height` in `barcode.ts` to suit.

### Can I print to a thermal label printer?

Yes. Render the PDF at 288 × 432 points, the 4 × 6 inch stock most desktop thermal printers take,
and print it at actual size. The bars are vector paths, so the printer draws them at its own
resolution rather than scaling a picture.

### Can I generate the labels as a PDF?

Yes, and it’s the format a print queue takes. `@quario/pdf` renders this definition straight to
bytes with no browser anywhere, so a dispatch job can print the labels as the shipment closes.
[Full example →](https://getquario.com/guides/json-to-pdf.md)

### Can I use this shipping label 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. Labelling real parcels is past evaluation, so
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) — packing slips, bills of lading, delivery notes, 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":"Shipping label","item":"https://getquario.com/templates/shipping-label"}]}
```
