210 × 297 mm
Packing slips
E-commerce packing slip
An A4 packing slip for online orders: an order-number barcode, ship-to and order details, items with SKU, variant, quantity and tick boxes, a note for items shipping in another parcel, an optional gift message and returns steps written in Markdown.
Opens Dynamic Document API with this template preloaded in a free workspace.
Template language features
- Loops
- Conditionals
- Filters
- Date formatting
- Barcode
- Markdown
- Repeating table headers
Filters such as format_currency and qrcode are described in the template language reference.
Variables
Derived from the sample data: 7 top-level fields, 30 paths in total. Send the same shape as data.
| Path | Type | Example |
|---|---|---|
| store | object | |
| store.name | string | "Hollowmere Goods" |
| store.tagline | string | "Homeware for slow mornings" |
| store.website | string | "hollowmere.example.com" |
| store.email | string | "hello@hollowmere.example.com" |
| store.returns_address | string | "Hollowmere Returns ⏎ Unit 9, Larkspur Trading …" |
| order | object | |
| order.number | string | "HM-104729" |
| order.placed_on | date | "2026-09-12" |
| order.packed_on | date | "2026-09-14" |
| order.shipping_method | string | "Tracked 48, no signature" |
| order.parcel_number | number | 1 |
| order.parcel_count | number | 2 |
| order.is_gift | boolean | true |
| customer | object | |
| customer.name | string | "Tomasz Wrenley" |
| customer.email | string | "t.wrenley@example.net" |
| ship_to | object | |
| ship_to.name | string | "Mira Castellane" |
| ship_to.address | string | "Flat 3, 41 Carrow Terrace ⏎ Dundee DD2 1LX ⏎ U…" |
| gift_message | object | |
| gift_message.text | string | "Happy housewarming, Mira! A few things for slo…" |
| gift_message.from | string | "Tomasz" |
| items | array<object> · 8 | |
| items[].sku | string | "HMG-MUG-OAT-35" |
| items[].name | string | "Stoneware mug" |
| items[].variant | string | "Oat glaze · 350 ml" |
| items[].quantity | number | 4 |
| items[].parcel | number | 1 |
| returns_instructions | string | "**Changed your mind?** Unused items can come b…" |
Sample data
Fictional records used for previews. Replace them with your own data when you call the API.
{
"store": {
"name": "Hollowmere Goods",
"tagline": "Homeware for slow mornings",
"website": "hollowmere.example.com",
"email": "hello@hollowmere.example.com",
"returns_address": "Hollowmere Returns\nUnit 9, Larkspur Trading Estate\nThetfield TF9 3QA\nUnited Kingdom"
},
"order": {
"number": "HM-104729",
"placed_on": "2026-09-12",
"packed_on": "2026-09-14",
"shipping_method": "Tracked 48, no signature",
"parcel_number": 1,
"parcel_count": 2,
"is_gift": true
},
"customer": {
"name": "Tomasz Wrenley",
"email": "t.wrenley@example.net"
},
"ship_to": {
"name": "Mira Castellane",
"address": "Flat 3, 41 Carrow Terrace\nDundee DD2 1LX\nUnited Kingdom"
},
"gift_message": {
"text": "Happy housewarming, Mira! A few things for slow Sunday breakfasts in the new flat.\nSee you at the end of the month.",
"from": "Tomasz"
},
"items": [
{ "sku": "HMG-MUG-OAT-35", "name": "Stoneware mug", "variant": "Oat glaze · 350 ml", "quantity": 4, "parcel": 1 },
{ "sku": "HMG-BWL-SAL-18", "name": "Serving bowl", "variant": "Salt glaze · 18 cm", "quantity": 1, "parcel": 1 },
{ "sku": "HMG-LIN-NAP-SGE", "name": "Linen napkins, set of four", "variant": "Sage", "quantity": 2, "parcel": 1 },
{ "sku": "HMG-BRD-OLV-40", "name": "Olive wood serving board", "variant": "40 × 18 cm", "quantity": 1, "parcel": 1 },
{ "sku": "HMG-CAN-FIG-200", "name": "Soy wax candle", "variant": "Fig leaf · 200 g", "quantity": 2, "parcel": 1 },
{ "sku": "HMG-TWL-HRB-NAT", "name": "Waffle tea towel", "variant": "Herringbone · natural", "quantity": 3, "parcel": 1 },
{ "sku": "HMG-JAR-HNY-250", "name": "Glass honey jar with dipper", "variant": "250 ml", "quantity": 1, "parcel": 1 },
{ "sku": "HMG-PLT-SPK-22", "name": "Speckled side plate", "variant": "Charcoal · 22 cm", "quantity": 4, "parcel": 2 }
],
"returns_instructions": "**Changed your mind?** Unused items can come back within 30 days of delivery, in their original packaging.\n\n1. Go to **hollowmere.example.com/returns** and enter your order number and delivery postcode.\n2. Choose a drop-off point or book a collection. We email you a prepaid label.\n3. Pack the items securely, attach the label and keep your proof of postage.\n\nRefunds go back to the original payment method within five working days of the parcel reaching us. Gift recipients receive store credit instead."
}Source
The body markup with Jinja, the stylesheet placed in <head>.
{#- Online-store packing slip. Lists the whole order and marks items that travel in another parcel. -#}
{%- set locale = "en-GB" -%}
{%- set this_parcel = order.parcel_number -%}
{%- set parcel_items = items | selectattr("parcel", "eq", this_parcel) | list -%}
{%- set units_here = parcel_items | sum_by("quantity") | int -%}
{%- set units_total = items | sum_by("quantity") | int -%}
<header class="masthead">
<div class="brand">
<svg class="logo" viewBox="0 0 40 40" aria-hidden="true">
<circle cx="20" cy="20" r="19" fill="#b0532c"/>
<path d="M20 31.5V11" stroke="#f4ede3" stroke-width="1.8" stroke-linecap="round"/>
<path d="M20 17.5c-4.2-.6-6.6-3.4-6.8-7.2 4.2.2 6.6 3 6.8 7.2zM20 22.5c4.2-.6 6.6-3.4 6.8-7.2-4.2.2-6.6 3-6.8 7.2zM20 27.5c-4.2-.6-6.6-3.4-6.8-7.2 4.2.2 6.6 3 6.8 7.2z" fill="#f4ede3"/>
</svg>
<div>
<div class="brand-name">{{ store.name }}</div>
<div class="brand-tagline">{{ store.tagline }}</div>
</div>
</div>
<div class="doc">
<h1>Packing slip</h1>
<div class="barcode">{{ order.number | barcode("code128", alt="Barcode " ~ order.number) }}</div>
<div class="doc-number">Order {{ order.number }}</div>
</div>
</header>
<section class="info">
<div>
<p class="label">Ship to</p>
<p class="name">{{ ship_to.name }}</p>
<p>{{ ship_to.address | nl2br }}</p>
</div>
<div>
<p class="label">Order</p>
<dl>
<dt>Placed</dt><dd>{{ order.placed_on | format_date("long", locale) }}</dd>
<dt>Packed</dt><dd>{{ order.packed_on | format_date("long", locale) }}</dd>
<dt>Delivery</dt><dd>{{ order.shipping_method }}</dd>
<dt>Parcel</dt><dd><strong>{{ this_parcel }} of {{ order.parcel_count }}</strong></dd>
</dl>
</div>
<div>
<p class="label">Ordered by</p>
<p class="name">{{ customer.name }}</p>
<p>{{ customer.email }}</p>
{%- if order.is_gift %}
<p class="gift-flag">
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2 6.5h12v2.5H2zM3 9h10v5.5H3z" fill="#b0532c"/><path d="M7.2 6.5h1.6v8H7.2z" fill="#f4ede3"/><path d="M8 6.5C6.6 3.6 3.9 3.8 4.3 5.5 4.6 6.6 8 6.5 8 6.5zm0 0c1.4-2.9 4.1-2.7 3.7-1C11.4 6.6 8 6.5 8 6.5z" fill="none" stroke="#b0532c" stroke-width="1.1"/></svg>
Gift order · no prices enclosed
</p>
{%- endif %}
</div>
</section>
{%- if order.is_gift and gift_message.text %}
<section class="gift">
<p class="label">A message for {{ ship_to.name }}</p>
<p class="gift-text">{{ gift_message.text | nl2br }}</p>
<p class="gift-from">From {{ gift_message.from }}</p>
</section>
{%- endif %}
<table class="items">
<thead>
<tr>
<th class="check" scope="col">Packed</th>
<th scope="col">Item</th>
<th scope="col">Variant</th>
<th scope="col">SKU</th>
<th class="qty" scope="col">Qty</th>
</tr>
</thead>
<tbody>
{%- for item in items %}
{%- set elsewhere = item.parcel != this_parcel %}
<tr{% if elsewhere %} class="elsewhere"{% endif %}>
<td class="check"><span class="box" role="img" aria-label="Tick box"></span></td>
<td>
<span class="item-name">{{ item.name }}</span>
{%- if elsewhere %}
<span class="badge">Ships separately in parcel {{ item.parcel }} of {{ order.parcel_count }}</span>
{%- endif %}
</td>
<td class="variant">{{ item.variant }}</td>
<td class="sku">{{ item.sku }}</td>
<td class="qty">{{ item.quantity }}</td>
</tr>
{%- endfor %}
</tbody>
</table>
<p class="count">
<strong>{{ units_here }} {{ "item" if units_here == 1 else "items" }} in this parcel</strong>
· {{ units_total }} in the whole order
</p>
<section class="lower">
<div class="returns">
<h2>Returns</h2>
{{ returns_instructions | markdown }}
</div>
<aside>
<div class="initials">
<div><span class="label">Packed by</span><span class="write"></span></div>
<div><span class="label">Checked by</span><span class="write"></span></div>
</div>
<p class="label">Return address</p>
<p class="address">{{ store.returns_address | nl2br }}</p>
</aside>
</section>
<footer class="thanks">
Thank you for shopping with {{ store.name }} · {{ store.website }} · {{ store.email }}
</footer><title>Packing slip – order {{ order.number }}</title>
<style>
:root {
--ink: #2e2923;
--muted: #7a6f63;
--rule: #e4dacd;
--sand: #f4ede3;
--terracotta: #b0532c;
--sage: #56704f;
}
* { box-sizing: border-box; }
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body {
margin: 0;
color: var(--ink);
font-family: "Lato", "Liberation Sans", Arial, sans-serif;
font-size: 9.2pt;
line-height: 1.42;
}
p { margin: 0; }
.label {
display: block;
margin-bottom: 1mm;
font-size: 6.8pt;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--terracotta);
}
.masthead { display: flex; justify-content: space-between; align-items: flex-start; }
.brand { display: flex; align-items: center; gap: 3.5mm; padding-top: 1mm; }
.logo { width: 13mm; height: 13mm; }
.brand-name { font-size: 15pt; font-weight: 900; line-height: 1.1; letter-spacing: 0.01em; }
.brand-tagline { font-size: 8.5pt; font-style: italic; color: var(--muted); }
.doc { text-align: right; }
.doc h1 { margin: 0 0 1.6mm; font-size: 20pt; font-weight: 300; line-height: 1; letter-spacing: 0.02em; }
.barcode img, .barcode svg { display: block; width: 50mm; height: 11.5mm; margin-left: auto; }
.doc-number { margin-top: 0.8mm; font-size: 8.2pt; font-weight: 700; letter-spacing: 0.04em; }
.info {
display: grid;
grid-template-columns: 1fr 1.3fr 1fr;
gap: 7mm;
margin: 6mm 0 5mm;
padding: 4.5mm 6mm;
border-radius: 3mm;
background: var(--sand);
}
.info .name { font-size: 10.2pt; font-weight: 700; }
.info dl { display: grid; grid-template-columns: auto 1fr; gap: 0.6mm 4mm; margin: 0; }
.info dt { color: var(--muted); }
.info dd { margin: 0; }
.gift-flag {
display: inline-flex;
align-items: center;
gap: 1.6mm;
margin-top: 2mm;
padding: 0.6mm 2.6mm;
border: 0.3mm solid var(--terracotta);
border-radius: 4mm;
font-size: 7.3pt;
font-weight: 700;
color: var(--terracotta);
}
.gift-flag svg { width: 3.4mm; height: 3.4mm; }
.gift {
position: relative;
margin-bottom: 5mm;
padding: 4mm 10mm 3.8mm;
border: 0.35mm solid var(--terracotta);
border-radius: 3mm;
text-align: center;
break-inside: avoid;
page-break-inside: avoid;
}
.gift::before {
content: "";
position: absolute;
top: 1.3mm;
right: 1.3mm;
bottom: 1.3mm;
left: 1.3mm;
border: 0.2mm dashed #dbb7a5;
border-radius: 2mm;
}
.gift-text { font-size: 11pt; font-style: italic; font-weight: 300; line-height: 1.45; }
.gift-from { margin-top: 1.5mm; font-size: 8.5pt; font-weight: 700; }
table.items { width: 100%; border-collapse: collapse; }
table.items thead { display: table-header-group; }
table.items th {
padding: 0 2mm 1.8mm;
border-bottom: 0.5mm solid var(--ink);
font-size: 6.8pt;
font-weight: 700;
letter-spacing: 0.12em;
text-align: left;
text-transform: uppercase;
color: var(--muted);
}
table.items td { padding: 2.1mm 2mm; border-bottom: 0.25mm solid var(--rule); vertical-align: middle; }
table.items tr { break-inside: avoid; page-break-inside: avoid; }
table.items .check { width: 15mm; padding-left: 0; }
.box { display: block; width: 4.4mm; height: 4.4mm; margin-left: 2mm; border: 0.35mm solid var(--ink); border-radius: 0.8mm; }
.item-name { display: block; font-weight: 700; }
table.items td.variant { color: var(--muted); }
table.items .sku { width: 40mm; }
table.items td.sku {
font-family: "DejaVu Sans Mono", "Liberation Mono", monospace;
font-size: 7.2pt;
color: var(--muted);
}
table.items .qty { width: 13mm; text-align: right; font-variant-numeric: tabular-nums; }
table.items td.qty { font-size: 12pt; font-weight: 900; }
tr.elsewhere .item-name, tr.elsewhere td.qty { color: var(--muted); }
tr.elsewhere .box { border-style: dashed; border-color: var(--muted); }
.badge {
display: inline-block;
margin-top: 0.8mm;
padding: 0.2mm 2mm;
border-radius: 3mm;
background: #e6ece2;
color: var(--sage);
font-size: 6.8pt;
font-weight: 700;
}
.count { margin-top: 2.5mm; font-size: 8.3pt; color: var(--muted); text-align: right; }
.count strong { color: var(--ink); }
.lower {
display: grid;
grid-template-columns: 1fr 56mm;
gap: 10mm;
margin-top: 4.5mm;
padding-top: 4mm;
border-top: 0.25mm solid var(--rule);
break-inside: avoid;
page-break-inside: avoid;
}
.returns h2 { margin: 0 0 1.5mm; font-size: 10.5pt; font-weight: 900; }
.returns p { margin: 0 0 1.6mm; font-size: 8.2pt; }
.returns ol { margin: 0 0 1.6mm; padding-left: 5mm; font-size: 8.2pt; }
.returns li { margin-bottom: 0.6mm; padding-left: 1mm; }
.returns li::marker { font-weight: 700; color: var(--terracotta); }
.initials { display: grid; grid-template-columns: 1fr 1fr; gap: 4mm; margin-bottom: 4mm; }
.write { display: block; height: 9mm; border: 0.3mm solid var(--rule); border-radius: 1.5mm; background: #fffdf9; }
.address { font-size: 8.2pt; color: var(--muted); }
/* The closing line goes with the returns section, never alone onto a page of its own. */
.thanks {
margin-top: 3.5mm;
padding-top: 2mm;
break-before: avoid;
page-break-before: avoid;
border-top: 0.25mm solid var(--rule);
font-size: 7.8pt;
color: var(--muted);
text-align: center;
}
</style>Render it with the API
After adding the template to your workspace, render it by ID and pass your data. Arrays are shortened here.
curl -X POST https://api-eu.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"input": {
"type": "template",
"template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C"
},
"data": {
"store": {
"name": "Hollowmere Goods",
"tagline": "Homeware for slow mornings",
"website": "hollowmere.example.com",
"email": "hello@hollowmere.example.com",
"returns_address": "Hollowmere Returns\nUnit 9, Larkspur Trading Estate\nThetfield TF9 3QA\nUnited Kingdom"
},
"order": {
"number": "HM-104729",
"placed_on": "2026-09-12",
"packed_on": "2026-09-14",
"shipping_method": "Tracked 48, no signature",
"parcel_number": 1,
"parcel_count": 2,
"is_gift": true
},
"customer": {
"name": "Tomasz Wrenley",
"email": "t.wrenley@example.net"
},
"ship_to": {
"name": "Mira Castellane",
"address": "Flat 3, 41 Carrow Terrace\nDundee DD2 1LX\nUnited Kingdom"
},
"gift_message": {
"text": "Happy housewarming, Mira! A few things for slow Sunday breakfasts in the new flat.\nSee you at the end of the month.",
"from": "Tomasz"
},
"items": [
{
"sku": "HMG-MUG-OAT-35",
"name": "Stoneware mug",
"variant": "Oat glaze · 350 ml",
"quantity": 4,
"parcel": 1
},
{
"sku": "HMG-BWL-SAL-18",
"name": "Serving bowl",
"variant": "Salt glaze · 18 cm",
"quantity": 1,
"parcel": 1
}
],
"returns_instructions": "**Changed your mind?** Unused items can come back within 30 days of delivery, in their original packaging.\n\n1. Go to **hollowmere.example.com/returns** and enter your order number and delivery postcode.\n2. Choose a drop-off point or book a collection. We email you a prepaid label.\n3. Pack the items securely, attach the label and keep your proof of postage.\n\nRefunds go back to the original payment method within five working days of the parcel reaching us. Gift recipients receive store credit instead."
},
"output": {
"format": "pdf",
"filename": "packing-slip-ecommerce.pdf"
}
}'const response = await fetch("https://api-eu.dynamicdocumentapi.com/v1/renders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DYNAMIC_DOCUMENT_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"input": {
"type": "template",
"template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C"
},
"data": {
"store": {
"name": "Hollowmere Goods",
"tagline": "Homeware for slow mornings",
"website": "hollowmere.example.com",
"email": "hello@hollowmere.example.com",
"returns_address": "Hollowmere Returns\nUnit 9, Larkspur Trading Estate\nThetfield TF9 3QA\nUnited Kingdom"
},
"order": {
"number": "HM-104729",
"placed_on": "2026-09-12",
"packed_on": "2026-09-14",
"shipping_method": "Tracked 48, no signature",
"parcel_number": 1,
"parcel_count": 2,
"is_gift": true
},
"customer": {
"name": "Tomasz Wrenley",
"email": "t.wrenley@example.net"
},
"ship_to": {
"name": "Mira Castellane",
"address": "Flat 3, 41 Carrow Terrace\nDundee DD2 1LX\nUnited Kingdom"
},
"gift_message": {
"text": "Happy housewarming, Mira! A few things for slow Sunday breakfasts in the new flat.\nSee you at the end of the month.",
"from": "Tomasz"
},
"items": [
{
"sku": "HMG-MUG-OAT-35",
"name": "Stoneware mug",
"variant": "Oat glaze · 350 ml",
"quantity": 4,
"parcel": 1
},
{
"sku": "HMG-BWL-SAL-18",
"name": "Serving bowl",
"variant": "Salt glaze · 18 cm",
"quantity": 1,
"parcel": 1
}
],
"returns_instructions": "**Changed your mind?** Unused items can come back within 30 days of delivery, in their original packaging.\n\n1. Go to **hollowmere.example.com/returns** and enter your order number and delivery postcode.\n2. Choose a drop-off point or book a collection. We email you a prepaid label.\n3. Pack the items securely, attach the label and keep your proof of postage.\n\nRefunds go back to the original payment method within five working days of the parcel reaching us. Gift recipients receive store credit instead."
},
"output": {
"format": "pdf",
"filename": "packing-slip-ecommerce.pdf"
}
}),
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const result = await response.json();
console.log(result);import os
import uuid
import requests
response = requests.post(
"https://api-eu.dynamicdocumentapi.com/v1/renders",
headers={
"Authorization": f"Bearer {os.environ['DYNAMIC_DOCUMENT_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"input": {
"type": "template",
"template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C",
},
"data": {
"store": {
"name": "Hollowmere Goods",
"tagline": "Homeware for slow mornings",
"website": "hollowmere.example.com",
"email": "hello@hollowmere.example.com",
"returns_address": "Hollowmere Returns\nUnit 9, Larkspur Trading Estate\nThetfield TF9 3QA\nUnited Kingdom",
},
"order": {
"number": "HM-104729",
"placed_on": "2026-09-12",
"packed_on": "2026-09-14",
"shipping_method": "Tracked 48, no signature",
"parcel_number": 1,
"parcel_count": 2,
"is_gift": True,
},
"customer": {
"name": "Tomasz Wrenley",
"email": "t.wrenley@example.net",
},
"ship_to": {
"name": "Mira Castellane",
"address": "Flat 3, 41 Carrow Terrace\nDundee DD2 1LX\nUnited Kingdom",
},
"gift_message": {
"text": "Happy housewarming, Mira! A few things for slow Sunday breakfasts in the new flat.\nSee you at the end of the month.",
"from": "Tomasz",
},
"items": [
{
"sku": "HMG-MUG-OAT-35",
"name": "Stoneware mug",
"variant": "Oat glaze · 350 ml",
"quantity": 4,
"parcel": 1,
},
{
"sku": "HMG-BWL-SAL-18",
"name": "Serving bowl",
"variant": "Salt glaze · 18 cm",
"quantity": 1,
"parcel": 1,
},
],
"returns_instructions": "**Changed your mind?** Unused items can come back within 30 days of delivery, in their original packaging.\n\n1. Go to **hollowmere.example.com/returns** and enter your order number and delivery postcode.\n2. Choose a drop-off point or book a collection. We email you a prepaid label.\n3. Pack the items securely, attach the label and keep your proof of postage.\n\nRefunds go back to the original payment method within five working days of the parcel reaching us. Gift recipients receive store credit instead.",
},
"output": {
"format": "pdf",
"filename": "packing-slip-ecommerce.pdf",
},
},
timeout=90,
)
response.raise_for_status()
print(response.json())