Skip to content

Template and Payload ​

Press supports splitting a document into two parts: a template that defines the design, and a payload that provides the content. This separation is the key to using Press at scale -- store a template once and generate thousands of documents by sending different payloads.

How It Works ​

A template contains the layout, styles, components, and page definitions:

xml
<!-- Template: stored on the platform -->
<press>
  <document format="A4" page-margin="2cm">
    <page>
      <frame direction="row" space-after-desired="24pt">
        <frame width="fill">
          <frame font-size="10pt">To: {{ data.recipient }}</frame>
          <frame font-size="10pt">Date: {{ data.date }}</frame>
        </frame>
        <img src="logo" max-height="40pt" />
      </frame>
      <flow name="body" />
      <frame space-before-desired="24pt">
        <p>Yours sincerely,</p>
        <p font-style="bold">{{ data.sender }}</p>
      </frame>
    </page>
  </document>

  <assets>
    <images>
      <logo>https://examples.papermill.io/papermill_logo.png</logo>
    </images>
  </assets>

  <styles>
    <document>
      <font>Inter</font>
      <font-size>10pt</font-size>
      <line-height>1.5</line-height>
    </document>
  </styles>
</press>

A payload provides just the parts that change:

xml
<!-- Payload: sent via API -->
<press>
  <flows>
    <body type="markdown">
      Thank you for your application to the Graduate Programme.

      I'm delighted to confirm that you have been accepted. Your start
      date is 1 September 2026. Please find enclosed the onboarding
      documents for your review.

      We look forward to welcoming you to the team.
    </body>
  </flows>

  <data type="json">
  {
    "recipient": "Dr. A. Patel",
    "sender": "R. Whitfield, HR Director",
    "date": "15 January 2026"
  }
  </data>
</press>

Merging Rules ​

When Press receives both a template and a payload, it merges them:

Top-Level ElementMerge Behaviour
<flows>Payload flows replace template flows with the same name. New flows are added.
<data>Merged shallowly. Payload fields override template fields with the same key.
<components>Merged. Payload components are added to or override template components.
<styles>Template styles are used. Payload styles are merged in.
<assets>Template assets are used. Not presently overridden by payloads.
<document>Template document structure is used. Not overridden.
<pages>Template page definitions are used. Not overridden.

Data Merging in Detail ​

Template:

json
{ "currency": "£", "tax_rate": 20, "company": "Default Corp" }

Payload:

json
{ "company": "Meridian Consulting", "client": "Helena Torres" }

Result:

json
{ "currency": "£", "tax_rate": 20, "company": "Meridian Consulting", "client": "Helena Torres" }

The payload's company wins, template-only fields (currency, tax_rate) are preserved, and new fields (client) are added.

Expressions in Data ​

Expressions in the template's top-level <data> resolve after the payload's data merges in, so a template value can use a payload value:

json
{ "title": "Invoice for {{ data.client }}" }

With the payload above, data.title is Invoice for Helena Torres. A value can refer to another value that holds an expression, in any order. Papermill rejects the document if a chain of references leads back to where it started.

A payload carries values, not expressions: Papermill rejects a payload whose data holds {{ … }}. Compute the value before you send it, or put the expression in the template.

Using Template and Payload via the API ​

  1. Create your template on the Papermill platform
  2. Copy the template ID from the editor URL
  3. Send a payload via the API with the template ID:
javascript
const payload = `
  <press>
    <flows>
      <body>Thank you for your application...</body>
    </flows>
    <data>
      <recipient>Dr. A. Patel</recipient>
      <sender>R. Whitfield</sender>
      <date>15 January 2026</date>
    </data>
  </press>
`

const response = await fetch(`https://api.papermill.io/v2/pdf?template=${templateId}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'text/xml',
    'X-API-Key': apiKey,
  },
  body: payload,
})

Benefits ​

  • Designers control the template: layout, fonts, colours, page structure
  • Developers control the payload: content, data, conditional sections
  • AI models can generate markdown payloads without needing to understand the template structure
  • One template generates unlimited variations by changing the payload

Best Practices ​

Put defaults in the template. If most invoices use GBP, set "currency": "£" in the template's <data>. Payloads only need to override it when the currency differs.

Use asset data for constants. Values that should never change (company logos, legal text) go in <assets><data>. These aren't affected by payload merging.

Keep payloads minimal. Only send the data and flows that change. The template handles everything else.

Use markdown flows for AI-generated content. AI models produce clean markdown. The template handles all formatting and layout.