Skip to content

210 × 297 mm

Purchase orders

Standard purchase order

An A4 purchase order with buyer, supplier and delivery addresses, a required-by date calculated from the lead time, numbered order lines, subtotal, VAT and total derived from the lines, delivery instructions and an approval block that adds finance sign-off above a threshold.

A4 portraitLocale en-GBHTML + JinjaHeader & footer

Opens Dynamic Document API with this template preloaded in a free workspace.

Template language features

  • Loops
  • Conditionals
  • Macros
  • Filters
  • Currency formatting
  • Date formatting
  • Repeating table headers
  • Header & footer
  • Page numbers

Filters such as format_currency and qrcode are described in the template language reference.

Variables

Derived from the sample data: 9 top-level fields, 44 paths in total. Send the same shape as data.

PathTypeExample
buyerobject
buyer.namestring"Stackyard Brewing Co. Ltd"
buyer.addressstring"The Maltings, 7 Stackyard Lane ⏎ Kendal LA9 4T…"
buyer.vat_idstring"GB 318 2264 50"
buyer.invoice_emailstring"payables@stackyard.example.com"
buyer_contactobject
buyer_contact.namestring"Rhiannon Tuckett"
buyer_contact.rolestring"Procurement Lead"
buyer_contact.emailstring"rhiannon.tuckett@stackyard.example.com"
buyer_contact.phonestring"+44 20 7946 0852"
supplierobject
supplier.namestring"Pellow & Grange Packaging Ltd"
supplier.accountstring"SUP-00412"
supplier.addressstring"Unit 14, Brindley Road Industrial Estate ⏎ War…"
supplier.emailstring"orders@pellowgrange.example.com"
deliver_toobject
deliver_to.namestring"Stackyard Brewing, Goods In"
deliver_to.addressstring"Bay 3, Kentmere Works, Quarry Row ⏎ Kendal LA9…"
deliver_to.hoursstring"Monday to Friday, 07:30–14:30"
deliver_to.contactstring"Warehouse team, +44 20 7946 0853"
orderobject
order.numberstring"PO-SB-2026-0388"
order.datedate"2026-09-15"
order.lead_time_daysnumber21
order.supplier_quotestring"Q-PG-11873"
order.payment_termsstring"30 days from the end of the invoice month"
order.cost_centrestring"OPS-PACK"
order.currencystring"GBP"
order.vat_ratenumber0.2
linesarray<object> · 8
lines[].codestring"PG-CAN-440-MB"
lines[].descriptionstring"Aluminium cans, 440 ml, matt black, unprinted"
lines[].unitstring"pallet of 8,280"
lines[].quantitynumber6
lines[].unit_pricenumber812.4
lines[].amountnumber4874.4
delivery_instructionsarray<string> · 4"Book a delivery slot with the warehouse team a…"
conditionsstring"Quote the PO number on every delivery note and…"
approvalsarray<object> · 3
approvals[].stepstring"Requested by"
approvals[].namestring"Rhiannon Tuckett"
approvals[].rolestring"Procurement Lead"
approvals[].datedate"2026-09-14"
approvals[].required_abovenumber5000

Sample data

Fictional records used for previews. Replace them with your own data when you call the API.

data.json
{
  "buyer": {
    "name": "Stackyard Brewing Co. Ltd",
    "address": "The Maltings, 7 Stackyard Lane\nKendal LA9 4TR\nUnited Kingdom",
    "vat_id": "GB 318 2264 50",
    "invoice_email": "payables@stackyard.example.com"
  },
  "buyer_contact": {
    "name": "Rhiannon Tuckett",
    "role": "Procurement Lead",
    "email": "rhiannon.tuckett@stackyard.example.com",
    "phone": "+44 20 7946 0852"
  },
  "supplier": {
    "name": "Pellow & Grange Packaging Ltd",
    "account": "SUP-00412",
    "address": "Unit 14, Brindley Road Industrial Estate\nWarrington WA2 8QZ\nUnited Kingdom",
    "email": "orders@pellowgrange.example.com"
  },
  "deliver_to": {
    "name": "Stackyard Brewing, Goods In",
    "address": "Bay 3, Kentmere Works, Quarry Row\nKendal LA9 6QQ\nUnited Kingdom",
    "hours": "Monday to Friday, 07:30–14:30",
    "contact": "Warehouse team, +44 20 7946 0853"
  },
  "order": {
    "number": "PO-SB-2026-0388",
    "date": "2026-09-15",
    "lead_time_days": 21,
    "supplier_quote": "Q-PG-11873",
    "payment_terms": "30 days from the end of the invoice month",
    "cost_centre": "OPS-PACK",
    "currency": "GBP",
    "vat_rate": 0.2
  },
  "lines": [
    { "code": "PG-CAN-440-MB", "description": "Aluminium cans, 440 ml, matt black, unprinted", "unit": "pallet of 8,280", "quantity": 6, "unit_price": 812.4, "amount": 4874.4 },
    { "code": "PG-END-202-SL", "description": "Can ends, 202 diameter, silver", "unit": "box of 6,000", "quantity": 8, "unit_price": 96.0, "amount": 768.0 },
    { "code": "PG-CRT-4PK-PR", "description": "Four-pack paperboard carriers, printed two colours", "unit": "1,000 pieces", "quantity": 12, "unit_price": 64.5, "amount": 774.0 },
    { "code": "PG-SHP-24-KFT", "description": "Shipping cartons for 24 × 440 ml cans, kraft", "unit": "bundle of 250", "quantity": 10, "unit_price": 87.25, "amount": 872.5 },
    { "code": "PG-KEG-CLR-30", "description": "Keg collars, printed, for 30 l kegs", "unit": "pack of 500", "quantity": 4, "unit_price": 38.9, "amount": 155.6 },
    { "code": "PG-WRP-23-CL", "description": "Pallet stretch film, 23 µm, clear", "unit": "roll", "quantity": 24, "unit_price": 14.8, "amount": 355.2 },
    { "code": "PG-LBL-TE-60", "description": "Tamper-evident labels, 60 mm", "unit": "roll of 1,000", "quantity": 15, "unit_price": 11.6, "amount": 174.0 },
    { "code": "PG-ART-PROOF", "description": "Artwork proofing and print plate set-up for carriers", "unit": "job", "quantity": 1, "unit_price": 240.0, "amount": 240.0 }
  ],
  "delivery_instructions": [
    "Book a delivery slot with the warehouse team at least 48 hours in advance.",
    "Deliver on standard 1,200 × 1,000 mm pallets, stretch-wrapped, no higher than 1.8 m.",
    "Label each pallet with this PO number and the line numbers it contains.",
    "Cans and ends must arrive sealed in food-grade packaging, with batch certificates."
  ],
  "conditions": "Quote the PO number on every delivery note and invoice. Part deliveries only by prior agreement. All goods are inspected on arrival and may be returned if they do not match this order.",
  "approvals": [
    { "step": "Requested by", "name": "Rhiannon Tuckett", "role": "Procurement Lead", "date": "2026-09-14", "required_above": null },
    { "step": "Budget holder", "name": "Callum Everingham", "role": "Head of Operations", "date": "2026-09-15", "required_above": null },
    { "step": "Finance approval", "name": "Priti Sandhu-Morrow", "role": "Finance Controller", "date": "2026-09-15", "required_above": 5000.0 }
  ]
}

Source

The body markup with Jinja, the stylesheet placed in <head> and the header and footer settings.

{#- Standard purchase order. Subtotal, VAT and total come from the order lines; the required-by date from the lead time. -#}
{%- set locale = "en-GB" -%}
{%- set currency = order.currency -%}
{%- set subtotal = lines | sum_by("amount") -%}
{%- set vat = (subtotal * order.vat_rate) | round(2) -%}
{%- set total = subtotal + vat -%}
{%- set required_by = order.date | date_add(days=order.lead_time_days) -%}

{%- macro address_card(label, name, address, extra=none) -%}
<div class="card">
  <p class="label">{{ label }}</p>
  <p class="card-name">{{ name }}</p>
  <p>{{ address | nl2br }}</p>
  {%- if extra %}
  <p class="card-extra">{{ extra }}</p>
  {%- endif %}
</div>
{%- endmacro %}

<header class="masthead">
  <div>
    <p class="buyer-name">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20V9l8-5 8 5v11" fill="none" stroke="#1d5b45" stroke-width="2.2" stroke-linejoin="round"/><path d="M8.5 20v-6h7v6M8.5 17h7" fill="none" stroke="#1d5b45" stroke-width="2.2"/></svg>
      {{ buyer.name }}
    </p>
    <h1>Purchase order</h1>
  </div>
  <div class="po-box">
    <span class="po-label">PO number</span>
    <span class="po-number">{{ order.number }}</span>
    <span class="po-date">Issued {{ order.date | format_date("long", locale) }}</span>
  </div>
</header>

<section class="cards">
  {{ address_card("Buyer", buyer.name, buyer.address, "VAT " ~ buyer.vat_id) }}
  {{ address_card("Supplier", supplier.name, supplier.address, "Account " ~ supplier.account ~ " · " ~ supplier.email) }}
  {{ address_card("Deliver to", deliver_to.name, deliver_to.address, deliver_to.hours) }}
</section>

<section class="meta">
  <div><span>Required by</span><strong>{{ required_by | format_date("long", locale) }}</strong></div>
  <div><span>Lead time</span><strong>{{ order.lead_time_days }} days</strong></div>
  <div><span>Supplier quote</span><strong>{{ order.supplier_quote }}</strong></div>
  <div><span>Cost centre</span><strong>{{ order.cost_centre }}</strong></div>
  <div class="wide"><span>Payment terms</span><strong>{{ order.payment_terms }}</strong></div>
  <div class="wide"><span>Buyer contact</span><strong>{{ buyer_contact.name }}, {{ buyer_contact.role }}</strong><em>{{ buyer_contact.email }} · {{ buyer_contact.phone }}</em></div>
</section>

<table class="lines">
  <thead>
    <tr>
      <th class="ln" scope="col">Line</th>
      <th class="code" scope="col">Item code</th>
      <th scope="col">Description</th>
      <th class="num" scope="col">Qty</th>
      <th scope="col">Unit</th>
      <th class="num" scope="col">Unit price</th>
      <th class="num" scope="col">Amount</th>
    </tr>
  </thead>
  <tbody>
    {%- for line in lines %}
    <tr>
      <td class="ln">{{ "%02d" | format(loop.index) }}</td>
      <td class="code">{{ line.code }}</td>
      <td>{{ line.description }}</td>
      <td class="num">{{ line.quantity }}</td>
      <td class="unit">{{ line.unit }}</td>
      <td class="num">{{ line.unit_price | format_currency(currency, locale) }}</td>
      <td class="num">{{ line.amount | format_currency(currency, locale) }}</td>
    </tr>
    {%- endfor %}
  </tbody>
</table>

<section class="totals-row">
  <p class="conditions">{{ conditions }}</p>
  <table class="totals">
    <tr><th scope="row">Subtotal ({{ lines | length }} lines)</th><td class="num">{{ subtotal | format_currency(currency, locale) }}</td></tr>
    <tr><th scope="row">VAT at {{ order.vat_rate | format_percent(locale) }}</th><td class="num">{{ vat | format_currency(currency, locale) }}</td></tr>
    <tr class="grand"><th scope="row">Order total</th><td class="num">{{ total | format_currency(currency, locale) }}</td></tr>
  </table>
</section>

<section class="lower">
  <div class="instructions">
    <h2>Delivery instructions</h2>
    <ol>
      {%- for step in delivery_instructions %}
      <li>{{ step }}</li>
      {%- endfor %}
    </ol>
    <p class="contact">On-site contact: {{ deliver_to.contact }}</p>
    <p class="contact">Send invoices to {{ buyer.invoice_email }}, quoting {{ order.number }}.</p>
  </div>

  <div class="approvals">
    <h2>Approval</h2>
    {%- for approval in approvals %}
    {%- if not approval.required_above or total > approval.required_above %}
    <div class="approver">
      <div class="who">
        <span class="step">{{ approval.step }}</span>
        <strong>{{ approval.name }}</strong>
        <span class="role">{{ approval.role }}</span>
        {%- if approval.required_above %}
        <span class="rule">Needed for orders above {{ approval.required_above | format_currency(currency, locale) }}</span>
        {%- endif %}
      </div>
      <div class="sig"><span>Signature</span></div>
      <div class="when"><span>Date</span>{{ approval.date | format_date("%d/%m/%Y", locale) }}</div>
    </div>
    {%- endif %}
    {%- endfor %}
  </div>
</section>

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": {
    "buyer": {
      "name": "Stackyard Brewing Co. Ltd",
      "address": "The Maltings, 7 Stackyard Lane\nKendal LA9 4TR\nUnited Kingdom",
      "vat_id": "GB 318 2264 50",
      "invoice_email": "payables@stackyard.example.com"
    },
    "buyer_contact": {
      "name": "Rhiannon Tuckett",
      "role": "Procurement Lead",
      "email": "rhiannon.tuckett@stackyard.example.com",
      "phone": "+44 20 7946 0852"
    },
    "supplier": {
      "name": "Pellow & Grange Packaging Ltd",
      "account": "SUP-00412",
      "address": "Unit 14, Brindley Road Industrial Estate\nWarrington WA2 8QZ\nUnited Kingdom",
      "email": "orders@pellowgrange.example.com"
    },
    "deliver_to": {
      "name": "Stackyard Brewing, Goods In",
      "address": "Bay 3, Kentmere Works, Quarry Row\nKendal LA9 6QQ\nUnited Kingdom",
      "hours": "Monday to Friday, 07:30–14:30",
      "contact": "Warehouse team, +44 20 7946 0853"
    },
    "order": {
      "number": "PO-SB-2026-0388",
      "date": "2026-09-15",
      "lead_time_days": 21,
      "supplier_quote": "Q-PG-11873",
      "payment_terms": "30 days from the end of the invoice month",
      "cost_centre": "OPS-PACK",
      "currency": "GBP",
      "vat_rate": 0.2
    },
    "lines": [
      {
        "code": "PG-CAN-440-MB",
        "description": "Aluminium cans, 440 ml, matt black, unprinted",
        "unit": "pallet of 8,280",
        "quantity": 6,
        "unit_price": 812.4,
        "amount": 4874.4
      },
      {
        "code": "PG-END-202-SL",
        "description": "Can ends, 202 diameter, silver",
        "unit": "box of 6,000",
        "quantity": 8,
        "unit_price": 96,
        "amount": 768
      }
    ],
    "delivery_instructions": [
      "Book a delivery slot with the warehouse team at least 48 hours in advance.",
      "Deliver on standard 1,200 × 1,000 mm pallets, stretch-wrapped, no higher than 1.8 m."
    ],
    "conditions": "Quote the PO number on every delivery note and invoice. Part deliveries only by prior agreement. All goods are inspected on arrival and may be returned if they do not match this order.",
    "approvals": [
      {
        "step": "Requested by",
        "name": "Rhiannon Tuckett",
        "role": "Procurement Lead",
        "date": "2026-09-14",
        "required_above": null
      },
      {
        "step": "Budget holder",
        "name": "Callum Everingham",
        "role": "Head of Operations",
        "date": "2026-09-15",
        "required_above": null
      }
    ]
  },
  "output": {
    "format": "pdf",
    "filename": "purchase-order-standard.pdf"
  }
}'