Templates / Delivery note
Delivery note
Packing list with quantities, article numbers and a receipt signature. Lands in the parcel or gets emailed with the tracking number.
1 page, A4. Rendered in 56 ms. 10 inputs, all optional.
Inputs
Everything variable comes in through sys.inputs. Missing keys fall back to sensible defaults, so an agent can send only what it knows.
| Key | Sample value |
|---|---|
carrier | DHL, 00340434161234567 |
customer | Northwind Traders |
date | 2026-09-28 |
items | JSON, 229 chars |
logo | logo.png |
no | DN-88213 |
note | 2 cable trays follow next week (backorder). |
order | SO-4471 |
sender | Meridian Warehouse |
ship_to | Northwind Traders, Receiving, Kastanienallee 7, 10435 Berlin |
Render it
After copying the template into your account:
curl -X POST https://api.foliant.dev/v1/render \
-H "Authorization: Bearer $FOLIANT_API_KEY" \
-H "Content-Type: application/json" \
-o delivery-note.pdf -d @- <<'JSON'
{ "template": "delivery-note.typ",
"inputs": {
"carrier": "DHL, 00340434161234567",
"customer": "Northwind Traders",
"date": "2026-09-28",
"items": "[{\"sku\":\"MS-1001\",\"name\":\"Desk lamp, matte black\",\"ordered\":\"12\",\"shipped\":\"12\"},{\"sku\":\"MS-2043\",\"name\":\"Cable tray 120 cm\",\"ordered\":\"20\",\"shipped\":\"18\"},{\"sku\":\"MS-3300\",\"name\":\"Monitor arm, dual\",\"ordered\":\"6\",\"shipped\":\"6\"}]",
"logo": "logo.png",
"no": "DN-88213",
"note": "2 cable trays follow next week (backorder).",
"order": "SO-4471",
"sender": "Meridian Warehouse",
"ship_to": "Northwind Traders, Receiving, Kastanienallee 7, 10435 Berlin"
} }
JSON
Through MCP, render_document with template: "delivery-note.typ" and the same inputs; get_template or fetch lists the keys.
Source
template.typ, 25 lines
#let input(k, default: "") = sys.inputs.at(k, default: default)
#let items = json(bytes(input("items", default: "[]")))
#let logo = input("logo")
#set page(paper: "a4", margin: 20mm)
#set text(font: "Noto Sans", size: 10pt, fill: rgb("1a1917"))
#grid(columns: (1fr, auto), align: (left, right),
if logo != "" { image(logo, width: 36mm) } else { text(size: 15pt, weight: 700)[#input("sender")] },
[#text(size: 18pt, weight: 700)[Delivery note] \ #text(fill: luma(110))[#input("no")]])
#v(8mm)
#grid(columns: (1fr, 1fr, 1fr), gutter: 8mm,
[#text(size: 8pt, fill: luma(110))[SHIP TO] \ #input("ship_to")],
[#text(size: 8pt, fill: luma(110))[ORDER] \ #input("order") \ #text(size: 8pt, fill: luma(110))[DATE] \ #input("date")],
[#text(size: 8pt, fill: luma(110))[CARRIER] \ #input("carrier")])
#v(8mm)
#table(columns: (auto, 1fr, auto, auto, auto), align: (left, left, right, right, center), inset: 7pt,
stroke: (x, y) => if y == 0 { (bottom: 0.8pt) } else { (bottom: 0.3pt + luma(200)) },
[*SKU*], [*Article*], [*Ordered*], [*Shipped*], [*Check*],
..items.map(i => (raw(i.sku), i.name, i.ordered, if i.shipped != i.ordered { text(fill: rgb("b8341c"))[#i.shipped] } else { i.shipped }, sym.square)).flatten())
#v(5mm)
#if input("note") != "" [#text(size: 9.5pt)[*Note:* #input("note")]]
#v(1fr)
#grid(columns: (1fr, 1fr), gutter: 20mm,
[#line(length: 100%, stroke: 0.5pt) #text(size: 8.5pt, fill: luma(110))[Packed by, date]],
[#line(length: 100%, stroke: 0.5pt) #text(size: 8.5pt, fill: luma(110))[Received in good order: name, date, signature]])