Escape

Bill of lading template for JavaScript

A bill of lading is the carrier’s receipt for the goods. It names who sends them, who receives them, and who pays for the freight. It lists what the carrier loaded by handling unit and weight. The shipper, the driver, and the consignee each sign it. When a pallet arrives damaged, it’s the document everyone reaches for. This is a ready-made quario bill of lading — copy it, point it at your shipment, and render to PDF, Word, Excel, or HTML.

The template

The whole bill is a definition — data you can version and review. The three parties, the carrier line, the handling units, the hazardous-goods notice and the signature blocks are all in here. This is the exact file the preview below renders:

bill-of-lading.report.json
{
  "data": "$.bol.units[*]",
  "aggregates": {
    "units": "sum:[email protected]",
    "kg": "sum:[email protected]",
    "hazmat": "sum:[email protected] ? 1 : 0"
  },
  "style": { "family": "sans", "size": 10 },
  "header": [
    {
      "type": "split",
      "style": { "spaceAfter": 18 },
      "slots": [
        { "type": "image", "source": "=logo()", "fit": "width", "alt": "{{ $.input.bol.shipper.name }} logo", "width": 28 },
        { "type": "text", "value": "", "style": { "size": 8 } }
      ]
    },
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "Bill of lading", "width": 60,
          "style": { "family": "serif", "size": 19, "bold": true, "color": "#1e1f22" } },
        { "type": "text", "value": "No. {{ $.input.bol.number }}",
          "style": { "family": "mono", "size": 10, "bold": false, "color": "#1e1f22", "align": "right" } }
      ]
    },
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "Straight bill of lading, not negotiable", "width": 60,
          "style": { "size": 8, "bold": false, "color": "#989a97" } },
        { "type": "text", "value": [{ "value": "Issued " }, { "value": "{{ $.input.bol.issued }}" }],
          "style": { "size": 8, "bold": false, "color": "#989a97", "align": "right", "format": "date" } }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 34 }, { "width": 33 }, {}],
      "rows": [
        {
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97", "paddingTop": 40 },
          "slots": [{ "type": "text", "value": "Shipper" }, { "type": "text", "value": "Consignee" }, { "type": "text", "value": "Bill freight to" }]
        },
        {
          "style": { "size": 10, "bold": true, "color": "#1e1f22" },
          "slots": [
            { "type": "text", "value": "{{ $.input.bol.shipper.name }}" },
            { "type": "text", "value": "{{ $.input.bol.consignee.name }}" },
            { "type": "text", "value": "{{ $.input.bol.billTo.name }}" }
          ]
        },
        {
          "style": { "size": 10, "bold": false, "color": "#5e6062" },
          "slots": [
            { "type": "text", "value": "{{ $.input.bol.shipper.street }}\n{{ $.input.bol.shipper.city }}\n{{ $.input.bol.shipper.country }}" },
            { "type": "text", "value": "{{ $.input.bol.consignee.street }}\n{{ $.input.bol.consignee.city }}\n{{ $.input.bol.consignee.country }}" },
            { "type": "text", "value": "{{ $.input.bol.billTo.street }}\n{{ $.input.bol.billTo.city }}\n{{ $.input.bol.billTo.country }}" }
          ]
        }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 25 }, { "width": 25 }, { "width": 25 }, {}],
      "rows": [
        {
          "style": { "family": "mono", "size": 7, "bold": false, "uppercase": true, "color": "#989a97", "paddingTop": 30 },
          "slots": [
            { "type": "text", "value": "Carrier" },
            { "type": "text", "value": "Trailer" },
            { "type": "text", "value": "Seal" },
            { "type": "text", "value": "Freight terms" }
          ]
        },
        {
          "style": { "size": 10, "bold": false, "color": "#1e1f22", "paddingBottom": 40 },
          "slots": [
            { "type": "text", "value": "{{ $.input.bol.carrier.name }}" },
            { "type": "text", "value": "{{ $.input.bol.carrier.trailer }}" },
            { "type": "text", "value": "{{ $.input.bol.carrier.seal }}" },
            { "type": "text", "value": "{{ $.input.bol.terms }}" }
          ]
        }
      ]
    }
  ],
  "detail": {
    "header": {
      "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97",
        "borderBottomWidth": 0.5, "borderBottomStyle": "solid", "borderBottomColor": "#1e1f22" }
    },
    "row": { "style": { "color": "#5e6062" } },
    "columns": [
      { "header": { "value": "Units", "style": { "align": "right" } }, "value": "{{ @.qty }}", "width": 8,
        "style": { "align": "right", "color": "#1e1f22" } },
      { "header": "Package", "value": "{{ @.package }}", "width": 12 },
      { "header": "Description", "value": "{{ @.description }}", "width": 42, "style": { "color": "#1e1f22" } },
      { "header": "Dims (cm)", "value": "{{ @.dims }}", "width": 16 },
      { "header": "HM", "value": "{{ @.hazmat ? 'X' : '' }}", "width": 5, "style": { "bold": true, "color": "#1e1f22" } },
      { "header": { "value": "Weight (kg)", "style": { "align": "right" } }, "value": "{{ @.kg }}", "width": 17,
        "style": { "align": "right", "color": "#1e1f22", "format": { "kind": "number", "digits": 1 } } }
    ],
    "total": {
      "style": { "bold": true, "color": "#1e1f22", "paddingTop": 6,
        "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" },
      "rows": [
        {
          "cells": [
            { "value": "{{ $.units }}", "style": { "align": "right" } },
            { "value": "Handling units", "span": 4 },
            { "value": "{{ round($.kg, 1) }}", "style": { "align": "right", "format": { "kind": "number", "digits": 1 } } }
          ]
        }
      ]
    }
  },
  "footer": [
    {
      "type": "text",
      "value": "HM marks hazardous material: {{ $.hazmat }} {{ $.hazmat == 1 ? 'line' : 'lines' }}. The shipper's dangerous goods declaration travels with the driver.",
      "visible": "=$.hazmat > 0",
      "style": { "size": 8, "color": "#5e6062", "spaceBefore": 8 }
    },
    {
      "type": "split",
      "style": { "spaceBefore": 30 },
      "slots": [
        { "type": "text", "value": "Instructions", "width": 22,
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "{{ $.input.bol.instructions }}", "style": { "color": "#5e6062" } }
      ]
    },
    {
      "type": "split",
      "slots": [
        { "type": "text", "value": "Order", "width": 22,
          "style": { "family": "mono", "size": 7, "uppercase": true, "color": "#989a97" } },
        { "type": "text", "value": "{{ $.input.bol.orderRef }}", "style": { "color": "#5e6062" } }
      ]
    },
    {
      "type": "grid",
      "columns": [{ "width": 30 }, { "width": 5 }, { "width": 30 }, { "width": 5 }, {}],
      "rows": [
        {
          "style": { "paddingTop": 96 },
          "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" } },
            { "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": "Shipper, signature and date" },
            { "type": "text", "value": "" },
            { "type": "text", "value": "Carrier, signature and date" },
            { "type": "text", "value": "" },
            { "type": "text", "value": "Consignee, signature and date" }
          ]
        }
      ]
    }
  ],
  "page": {
    "footer": [
      {
        "type": "text",
        "value": "Received in apparent good order and condition, except as noted, for carriage to the consignee named above. Report loss or damage on delivery, quoting {{ $.input.bol.number }}.",
        "style": { "size": 8, "color": "#989a97", "paddingTop": 10,
          "borderTopWidth": 0.5, "borderTopStyle": "solid", "borderTopColor": "#1e1f22" }
      }
    ]
  }
}

The three parties sit on one grid. It states its column widths once, and each row lays the label, the name, and the address across them. A second grid does the same for the carrier, the trailer, the seal, and the freight terms. Neither one needs a coordinate.

The table’s total row reads three report aggregates. $.units sums the handling units, and $.kg sums the weights, which round($.kg, 1) brings back to the one decimal the scale reads. $.hazmat counts the lines flagged as dangerous goods. The notice under the table declares "visible": "=$.hazmat > 0", so a load without one prints no empty warning.

Weights present through { "kind": "number", "digits": 1 }, so 212 reads 212.0 beside 486.5 and the column lines up. The cell still holds a number, which the Excel render keeps.

The data

A definition renders nothing on its own — you hand it a document. This is the sample the preview below uses. $.bol.units[*] in the definition selects its rows, and $.input reads the rest:

bill-of-lading.data.json
{
  "bol": {
    "number": "BL-2026-0418",
    "issued": "2026-08-27",
    "orderRef": "SO 2026-1042",
    "terms": "Prepaid",
    "carrier": { "name": "PostNL Cargo", "trailer": "NL 48-TRB-2", "seal": "0047721" },
    "shipper": {
      "name": "Quire Press Ltd.",
      "street": "Binnenkant 42",
      "city": "1011 BM Amsterdam",
      "country": "Netherlands"
    },
    "consignee": {
      "name": "Folio & Sons",
      "street": "12 Marlborough Street",
      "city": "Dublin 1",
      "country": "Ireland"
    },
    "billTo": {
      "name": "Quire Press Ltd.",
      "street": "Accounts payable, Binnenkant 42",
      "city": "1011 BM Amsterdam",
      "country": "Netherlands"
    },
    "instructions": "Tail-lift delivery. Keep dry; stack no more than two pallets high.",
    "units": [
      { "qty": 2, "package": "Pallet", "description": "Case-bound books, quarter cloth, shrink-wrapped", "dims": "120 × 80 × 110", "kg": 486.5, "hazmat": false },
      { "qty": 1, "package": "Pallet", "description": "Type specimen books, boxed", "dims": "120 × 80 × 90", "kg": 212.0, "hazmat": false },
      { "qty": 6, "package": "Carton", "description": "Proof copies and loose dust jackets", "dims": "60 × 40 × 40", "kg": 41.6, "hazmat": false },
      { "qty": 1, "package": "Drum", "description": "Printing ink, oil-based, 25 L", "dims": "Ø 30 × 40", "kg": 27.4, "hazmat": true }
    ]
  }
}

Each unit line carries its own weight, not a weight per piece. That’s what goes on the weighbridge ticket, and it’s what the carrier reconciles against.

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 bill as real @quario/pdf bytes, one A4 page with the terms on its baseline. The DOCX button beside it hands the same definition to @quario/docx, where Word paginates and resolves the faces itself:

Rendering live with @quario/viewerA4

Use it

shell
npm install quario @quario/pdf
bill-of-lading.ts
import { readFileSync, writeFileSync } from 'node:fs'
import { quario } from 'quario'
import { pdf } from '@quario/pdf'
import billOfLading from './bill-of-lading.report.json' with { type: 'json' }

const logo = () => readFileSync('logo.png')          // your logo, as PNG bytes
const report = quario({ locale: 'en-IE' }).report(billOfLading, { logo })

const data = await getShipment(id)                   // your shipment
const bytes = await report.render(pdf({ page: { size: 'A4', margin: 54 } }), data)

writeFileSync('bill-of-lading.pdf', bytes)

Print three copies, one for each signature, or render once and let each party sign its own. The issue date presents through the instance’s locale, and nothing on the bill is money, so the instance needs no currency.

Make it yours

Because the bill is declarative, changing it means changing data. A straight bill for American freight, with an NMFC class per line, is one more column. A declared value is one more field and a format: "currency" cell. An ocean bill swaps the trailer and seal for a vessel and container number. A CMR consignment note for road freight inside Europe keeps the same shape under different labels. It renders without eval, so a description copied from a supplier’s packing list never becomes code. Security →

FAQ

It’s the layout and the data, not the legal terms. The terms a bill carries depend on the mode of transport, the country and the carrier’s own conditions. Put the wording your carrier or your freight forwarder gives you in the page footer, where the sample’s short receipt clause sits.

How’s this different from a packing slip?

A packing slip goes inside the box and lists its contents for the recipient. A bill of lading travels with the driver and records what the carrier took on, by handling unit and weight, with signatures at both ends. Same shipment, different obligation.

Can I export the bill of lading to Excel?

Yes — @quario/xlsx renders the same definition to a workbook. The units and the weights stay numbers, so a warehouse can reconcile the loads against its own records. Full example →

Can I use this bill of lading 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 freight 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 → — shipping labels, packing slips, delivery notes, and more.