Skip to content

210 × 297 mm

Payslips

Monthly payslip

An A4 monthly payslip with employer and employee details, earnings and deductions tables with this-period and year-to-date columns, employer contributions, a net pay panel with the amount in words, payment details and annual leave. Sample figures are illustrative.

A4 portraitLocale en-GBHTML + Jinja

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

Template language features

  • Loops
  • Conditionals
  • Macros
  • Filters
  • Currency formatting
  • Number formatting
  • Date formatting

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
employerobject
employer.namestring"Fennick & Rowe Instruments Ltd"
employer.addressstring"Unit 2, Heathfield Science Park ⏎ Cambridge CB…"
employer.paye_referencestring"123/AB45678"
employer.payroll_emailstring"payroll@fennickrowe.example.com"
employeeobject
employee.namestring"Saoirse Pembridge"
employee.employee_numberstring"E-00417"
employee.job_titlestring"Senior Test Engineer"
employee.departmentstring"Test Engineering"
employee.ni_numberstring"QQ 12 34 56 C"
employee.tax_codestring"1257L"
employee.ni_categorystring"A"
employee.start_datedate"2021-04-12"
periodobject
period.labelstring"September 2026"
period.tax_yearstring"2026/27"
period.tax_periodnumber6
period.frequencystring"Monthly"
period.pay_datedate"2026-09-25"
period.currencystring"GBP"
earningsarray<object> · 4
earnings[].descriptionstring"Basic salary"
earnings[].detailstring"£46,800.00 a year"
earnings[].amountnumber3900
earnings[].ytdnumber23400
deductionsarray<object> · 5
deductions[].descriptionstring"Income tax"
deductions[].detailstring"Tax code 1257L"
deductions[].amountnumber604.46
deductions[].ytdnumber3654.88
employer_contributionsarray<object> · 2
employer_contributions[].descriptionstring"Employer National Insurance"
employer_contributions[].detailstring"Paid on your earnings"
employer_contributions[].amountnumber580.1
employer_contributions[].ytdnumber3502.8
paymentobject
payment.methodstring"Bank transfer"
payment.account_hintstring"account ending 4821"
leaveobject
leave.entitlement_daysnumber27
leave.taken_daysnumber12.5
leave.remaining_daysnumber14.5
messagestring"The pension scheme's annual statements are now…"

Sample data

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

data.json
{
  "employer": {
    "name": "Fennick & Rowe Instruments Ltd",
    "address": "Unit 2, Heathfield Science Park\nCambridge CB4 0XX\nUnited Kingdom",
    "paye_reference": "123/AB45678",
    "payroll_email": "payroll@fennickrowe.example.com"
  },
  "employee": {
    "name": "Saoirse Pembridge",
    "employee_number": "E-00417",
    "job_title": "Senior Test Engineer",
    "department": "Test Engineering",
    "ni_number": "QQ 12 34 56 C",
    "tax_code": "1257L",
    "ni_category": "A",
    "start_date": "2021-04-12"
  },
  "period": {
    "label": "September 2026",
    "tax_year": "2026/27",
    "tax_period": 6,
    "frequency": "Monthly",
    "pay_date": "2026-09-25",
    "currency": "GBP"
  },
  "earnings": [
    { "description": "Basic salary", "detail": "£46,800.00 a year", "amount": 3900.0, "ytd": 23400.0 },
    { "description": "Overtime at time and a half", "detail": "6.5 hours × £36.00", "amount": 234.0, "ytd": 1152.0 },
    { "description": "On-call allowance", "detail": "3 weekends × £50.00", "amount": 150.0, "ytd": 700.0 },
    { "description": "Quarterly bonus", "detail": "Paid in June", "amount": 0.0, "ytd": 600.0 }
  ],
  "deductions": [
    { "description": "Income tax", "detail": "Tax code 1257L", "amount": 604.46, "ytd": 3654.88 },
    { "description": "National Insurance", "detail": "Category A", "amount": 258.88, "ytd": 1565.12 },
    { "description": "Pension, employee contribution", "detail": "5% of gross pay", "amount": 214.2, "ytd": 1292.6 },
    { "description": "Student loan", "detail": "Plan 2", "amount": 172.0, "ytd": 1044.0 },
    { "description": "Cycle to work scheme", "detail": "Instalment 6 of 12", "amount": 41.5, "ytd": 249.0 }
  ],
  "employer_contributions": [
    { "description": "Employer National Insurance", "detail": "Paid on your earnings", "amount": 580.1, "ytd": 3502.8 },
    { "description": "Employer pension contribution", "detail": "8% of gross pay", "amount": 342.72, "ytd": 2068.16 }
  ],
  "payment": {
    "method": "Bank transfer",
    "account_hint": "account ending 4821"
  },
  "leave": {
    "entitlement_days": 27,
    "taken_days": 12.5,
    "remaining_days": 14.5
  },
  "message": "The pension scheme's annual statements are now available in the employee portal. Contact payroll by 5 October if any details on this payslip look wrong."
}

Source

The body markup with Jinja, the stylesheet placed in <head>.

{#- Monthly payslip. Totals and net pay are derived from the earnings and deduction lines; figures are illustrative. -#}
{%- set locale = "en-GB" -%}
{%- set currency = period.currency -%}
{%- set gross = earnings | sum_by("amount") -%}
{%- set gross_ytd = earnings | sum_by("ytd") -%}
{%- set total_deductions = deductions | sum_by("amount") -%}
{%- set total_deductions_ytd = deductions | sum_by("ytd") -%}
{%- set net_pay = gross - total_deductions -%}
{%- set net_pay_ytd = gross_ytd - total_deductions_ytd -%}
{%- set pence = ((net_pay * 100) | round | int) % 100 -%}

{%- macro money(value) -%}{{ value | format_currency(currency, locale) }}{%- endmacro -%}

{%- macro pay_table(title, rows, total_label, css_class, detail_column=true) -%}
<table class="pay {{ css_class }}">
  <thead>
    <tr>
      <th class="desc" scope="col">{{ title }}</th>
      {%- if detail_column %}
      <th class="detail" scope="col">Details</th>
      {%- endif %}
      <th class="num" scope="col">This period</th>
      <th class="num" scope="col">Year to date</th>
    </tr>
  </thead>
  <tbody>
    {%- for row in rows %}
    <tr>
      <th class="desc" scope="row">{{ row.description }}{% if not detail_column and row.detail %}<span class="sub">{{ row.detail }}</span>{% endif %}</th>
      {%- if detail_column %}
      <td class="detail">{{ row.detail }}</td>
      {%- endif %}
      <td class="num">{% if row.amount %}{{ money(row.amount) }}{% else %}<span class="nil">–</span>{% endif %}</td>
      <td class="num ytd">{{ money(row.ytd) }}</td>
    </tr>
    {%- endfor %}
  </tbody>
  <tfoot>
    <tr>
      <th colspan="{{ 2 if detail_column else 1 }}" scope="row">{{ total_label }}</th>
      <td class="num">{{ money(rows | sum_by("amount")) }}</td>
      <td class="num ytd">{{ money(rows | sum_by("ytd")) }}</td>
    </tr>
  </tfoot>
</table>
{%- endmacro %}

<header class="masthead">
  <div class="employer">
    <svg class="logo" viewBox="0 0 40 40" aria-hidden="true">
      <circle cx="20" cy="20" r="18" fill="#2f3a8f"/>
      <path d="M11 25a9 9 0 0 1 18 0" fill="none" stroke="#c7ccf2" stroke-width="2.6" stroke-linecap="round"/>
      <path d="M20 25l5-7" stroke="#ffffff" stroke-width="2.6" stroke-linecap="round"/>
      <circle cx="20" cy="25" r="2.2" fill="#ffffff"/>
    </svg>
    <div>
      <p class="employer-name">{{ employer.name }}</p>
      <p class="employer-meta">{{ employer.address | replace("\n", ", ") }}</p>
      <p class="employer-meta">Employer PAYE reference {{ employer.paye_reference }}</p>
    </div>
  </div>
  <div class="doc">
    <p class="confidential">Private and confidential</p>
    <h1>Payslip</h1>
    <p class="period">{{ period.label }}</p>
  </div>
</header>

<section class="employee">
  <div class="who">
    <p class="label">Employee</p>
    <p class="employee-name">{{ employee.name }}</p>
    <p>{{ employee.job_title }} · {{ employee.department }}</p>
  </div>
  <dl>
    <div><dt>Employee no.</dt><dd>{{ employee.employee_number }}</dd></div>
    <div><dt>NI number</dt><dd>{{ employee.ni_number }}</dd></div>
    <div><dt>Tax code</dt><dd>{{ employee.tax_code }}</dd></div>
    <div><dt>NI category</dt><dd>{{ employee.ni_category }}</dd></div>
    <div><dt>Pay date</dt><dd>{{ period.pay_date | format_date("long", locale) }}</dd></div>
    <div><dt>Tax period</dt><dd>Month {{ period.tax_period }}, {{ period.tax_year }}</dd></div>
  </dl>
</section>

{{ pay_table("Earnings", earnings, "Gross pay", "earnings") }}
{{ pay_table("Deductions", deductions, "Total deductions", "deductions") }}

<section class="net">
  <div class="net-main">
    <p class="label">Net pay</p>
    <p class="net-amount">{{ money(net_pay) }}</p>
    <p class="net-words">{{ (net_pay | int) | number_to_words("en") | capitalize }} pounds{% if pence %} and {{ pence }} pence{% endif %}</p>
  </div>
  <dl class="net-side">
    <div><dt>Paid by</dt><dd>{{ payment.method }} to {{ payment.account_hint }}</dd></div>
    <div><dt>Paid on</dt><dd>{{ period.pay_date | format_date("%A %d %B %Y", locale) }}</dd></div>
  </dl>
</section>

<section class="summary">
  <div><span>Gross pay</span><strong>{{ money(gross) }}</strong><em>{{ money(gross_ytd) }} year to date</em></div>
  <div class="minus"><span>Deductions</span><strong>−{{ money(total_deductions) }}</strong><em>{{ money(total_deductions_ytd) }} year to date</em></div>
  <div class="equals"><span>Net pay</span><strong>{{ money(net_pay) }}</strong><em>{{ money(net_pay_ytd) }} year to date</em></div>
</section>

<section class="lower">
  <div class="employer-costs">
    {{ pay_table("Paid by your employer", employer_contributions, "Employer contributions", "contributions", detail_column=false) }}
    <p class="hint">These amounts are paid by {{ employer.name }} on top of your pay and are not deducted from it.</p>
  </div>
  <aside class="info">
    <p class="label">Annual leave</p>
    <div class="leave">
      <span class="leave-bar"><span class="leave-fill" style="width: {{ (leave.taken_days / leave.entitlement_days * 100) | round(1) }}%"></span></span>
      <p><strong>{{ leave.remaining_days }} days</strong> remaining of {{ leave.entitlement_days }} ({{ leave.taken_days }} taken)</p>
    </div>
    {%- if message %}
    <p class="label">Message from payroll</p>
    <p class="message">{{ message }}</p>
    {%- endif %}
    <p class="contact">Questions: {{ employer.payroll_email }}</p>
  </aside>
</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": {
    "employer": {
      "name": "Fennick & Rowe Instruments Ltd",
      "address": "Unit 2, Heathfield Science Park\nCambridge CB4 0XX\nUnited Kingdom",
      "paye_reference": "123/AB45678",
      "payroll_email": "payroll@fennickrowe.example.com"
    },
    "employee": {
      "name": "Saoirse Pembridge",
      "employee_number": "E-00417",
      "job_title": "Senior Test Engineer",
      "department": "Test Engineering",
      "ni_number": "QQ 12 34 56 C",
      "tax_code": "1257L",
      "ni_category": "A",
      "start_date": "2021-04-12"
    },
    "period": {
      "label": "September 2026",
      "tax_year": "2026/27",
      "tax_period": 6,
      "frequency": "Monthly",
      "pay_date": "2026-09-25",
      "currency": "GBP"
    },
    "earnings": [
      {
        "description": "Basic salary",
        "detail": "£46,800.00 a year",
        "amount": 3900,
        "ytd": 23400
      },
      {
        "description": "Overtime at time and a half",
        "detail": "6.5 hours × £36.00",
        "amount": 234,
        "ytd": 1152
      }
    ],
    "deductions": [
      {
        "description": "Income tax",
        "detail": "Tax code 1257L",
        "amount": 604.46,
        "ytd": 3654.88
      },
      {
        "description": "National Insurance",
        "detail": "Category A",
        "amount": 258.88,
        "ytd": 1565.12
      }
    ],
    "employer_contributions": [
      {
        "description": "Employer National Insurance",
        "detail": "Paid on your earnings",
        "amount": 580.1,
        "ytd": 3502.8
      },
      {
        "description": "Employer pension contribution",
        "detail": "8% of gross pay",
        "amount": 342.72,
        "ytd": 2068.16
      }
    ],
    "payment": {
      "method": "Bank transfer",
      "account_hint": "account ending 4821"
    },
    "leave": {
      "entitlement_days": 27,
      "taken_days": 12.5,
      "remaining_days": 14.5
    },
    "message": "The pension scheme'\''s annual statements are now available in the employee portal. Contact payroll by 5 October if any details on this payslip look wrong."
  },
  "output": {
    "format": "pdf",
    "filename": "payslip-monthly.pdf"
  }
}'