Skip to content

Modern Report

A modern report template, pre-populated with data for a PropTech report but widely reusable for modern reporting.

Download Full PDF

modern-report-thumbs-selected

Key Features

This extensive template uses a combination of a long markdown flow and JSON for most of the document. For the financials flow, it uses the Press language instead, as it relies heavily on repetition and a grid layout. The financials flow is computed from data, so will update if the <data> input changes during the API request. By default, the financials flow is empty and doesn't render. When the correct data is set, this report incorporates visualisations generated by Papermill directly from JSON data.

Template Source

Copy and paste this source into the Papermill editor to preview the template.

Details
xml
<press>
  <document format="A4" page-margin-top="1.85cm">
    <coverpage />
    <repeat flow="toc-flow">
      <toc-right />
    </repeat>
    <repeat flow="body">
      <report-right />
    </repeat>
    <repeat flow="financials">
      <financials-right />
    </repeat>
    <back-matter />
  </document>

  <pages>
    <coverpage>
      <frame width="fill" direction="row" padding-left="1.85cm" padding-right="1.85cm" padding-bottom="1.85cm">
        <logo />
        <frame h-align="right" text-align="right">
          <b>Issue:</b> {{ data.issueDate }}
        </frame>
      </frame>
      <frame width="fill" height="fill" padding="1.85cm">
        <img right="0" bottom="0" width="fill" height="fill" scale="cover" src="{{ assets.data.splash }}" />
        <frame left="0" top="0" width="fill" height="fill" background-color="#143047CC" />
        <frame style="@coverTitle" width="75%" height="180pt" space-after-desired="12pt">
          <markdown inline="true">{{ data.title }}</markdown>
        </frame>
        <frame height="fill" font-color="white" width="47%" direction="row">
          <frame space-after-desired="8pt">
            <markdown inline="true">{{ data.cover.subtitle }}</markdown>
          </frame>
          <frame width="fill" padding-top="6pt">
            <hr />
          </frame>
        </frame>
        <frame width="35%" font-color="white" line-height="1.4">
          <frame>Prepared by:</frame>
          <frame><b>{{ data.author.name }}</b></frame>
          <frame>{{ data.author.address }}</frame>
          <frame>{{ data.author.postcode }}</frame>
        </frame>
      </frame>
    </coverpage>

    <toc-right deferred="true">
      <header-right />
      <frame space-after-desired="72pt" />
      <frame height="fill" width="fill" padding="1.85cm" padding-top="1.4cm" padding-bottom="1.4cm"
        background-color="primary">
        <flow deferred="true">
          <h1 font-color="white"><b>CONTENTS</b></h1>
        </flow>
        <frame direction="row" width="fill" height="fill" space-before-desired="40pt" font-color="white">
          <frame width="fill" height="fill">
            <flow name="toc-flow" />
          </frame>
          <frame width="26pt" />
          <frame width="fill" height="fill">
            <flow name="toc-flow" />
          </frame>
        </frame>
      </frame>
      <frame space-before-desired="72pt" width="fill" padding-bottom="1.85cm" padding-left="1.85cm"
        padding-right="1.85cm" max-height="8.2cm" padding-top="1.4cm">
        <flow name="contents-footer" />
      </frame>
    </toc-right>

    <report-right padding-bottom="1.85cm">
      <header-right />
      <frame padding-left="1.85cm" padding-right="1.85cm" padding-top="2.2cm">
        <flow name="body" />
      </frame>
    </report-right>

    <financials-right padding-bottom="1.85cm">
      <header-right />
      <frame padding-left="1.85cm" padding-right="1.85cm" padding-top="2.2cm">
        <flow name="financials" />
      </frame>
    </financials-right>

    <back-matter>
      <frame width="fill" direction="row" padding-left="1.85cm" padding-right="1.85cm" padding-bottom="1.85cm"
        height="3.5cm" />
      <frame width="fill" height="fill" padding="1.85cm" padding-top="5.8cm" font-color="white">
        <img right="0" bottom="0" width="fill" src="{{ assets.data.splash }}" />
        <frame left="0" top="0" width="fill" height="fill" background-color="#143047CC" />
        <logo />

        <frame font-color="white" space-before-desired="32pt" height="fill" width="50%">
          <frame direction="row">
            <frame space-after-desired="8pt">
              <markdown inline="true">{{ data.backMatter.tagline }}</markdown>
            </frame>
            <frame width="fill" padding-top="6pt">
              <hr />
            </frame>
          </frame>
          <p>
            {{ data.backMatter.description }}
          </p>
        </frame>
        <frame space-after-desired="48pt" direction="row" width="80%">
          <frame width="fill">
            <frame>Website</frame>
            <frame><b>{{ data.contact.website }}</b></frame>
          </frame>
          <frame width="fill">
            <frame>Email</frame>
            <frame><b>{{ data.contact.email }}</b></frame>
          </frame>
        </frame>
        <frame width="35%" font-color="white" line-height="1.4" font-size="11pt">
          <frame>Address:</frame>
          <frame>{{ data.contact.address }}</frame>
          <frame>{{ data.contact.postcode }}</frame>
        </frame>
      </frame>
    </back-matter>
  </pages>

  <assets>
    <colors>
      <primary>#143047</primary>
      <accent>#2DBA8F</accent>
      <text-black>#231F20</text-black>
      <grey>#E6E7E9</grey>
    </colors>
    <data type="json">
      {
        "baseUrl": "https://examples.papermill.io/proptech-whitepaper",
        "splash": "https://examples.papermill.io/proptech-whitepaper/splash.png"
      }
    </data>
  </assets>

  <styles>
    <document>
      <text-align>justified</text-align>
      <font>Lato</font>
      <font-color>text-black</font-color>
      <font-size>10pt</font-size>
    </document>
    <li>
      <orphan-lines-threshold>3</orphan-lines-threshold>
    </li>
    <h1>
      <case>title</case>
      <text-align>left</text-align>
      <width>fill</width>
      <font>Roboto</font>
      <font-size>40pt</font-size>
      <font-color>primary</font-color>
      <child name="b">
        <font-weight>black</font-weight>
      </child>
      <child name="strong">
        <font-weight>black</font-weight>
      </child>
      <break-before>frame</break-before>
      <space-before-desired>24pt</space-before-desired>
      <space-after-desired>22pt</space-after-desired>
    </h1>
    <h2>
      <text-align>left</text-align>
      <font>Roboto</font>
      <font-size>16pt</font-size>
      <font-color>primary</font-color>
      <font-weight>bold</font-weight>
      <space-before-desired>20pt</space-before-desired>
      <space-after-desired>10pt</space-after-desired>
    </h2>
    <h3>
      <text-align>left</text-align>
      <font>Roboto</font>
      <font-size>11pt</font-size>
      <font-color>text-black</font-color>
      <font-weight>semibold</font-weight>
      <case>upper</case>
      <tracking>0.08</tracking>
      <space-before-desired>14pt</space-before-desired>
      <space-after-desired>6pt</space-after-desired>
      <child name="strong">
        <font-weight>black</font-weight>
      </child>
      <child name="b">
        <font-weight>black</font-weight>
      </child>
    </h3>
    <h4>
      <font>Roboto</font>
    </h4>
    <h5>
      <font>Roboto</font>
    </h5>
    <h6>
      <font>Roboto</font>
    </h6>

    <p>
      <orphan-lines-threshold>4</orphan-lines-threshold>
      <font-weight>light</font-weight>
      <font-size>10pt</font-size>
      <line-height>1.6</line-height>
      <hyphens>true</hyphens>
      <text-align>justify</text-align>
      <space-before-desired>10pt</space-before-desired>
      <space-after-desired>10pt</space-after-desired>
      <child name="strong">
        <font-weight>bold</font-weight>
        <font-color>primary</font-color>
      </child>
      <child name="em">
        <font-style>italic</font-style>
      </child>
    </p>

    <ul>
      <padding-left>14pt</padding-left>
      <space-before-desired>10pt</space-before-desired>
      <space-after-desired>10pt</space-after-desired>
    </ul>
    <ol>
      <padding-left>14pt</padding-left>
      <space-before-desired>10pt</space-before-desired>
      <space-after-desired>10pt</space-after-desired>
    </ol>
    <li>
      <font-weight>light</font-weight>
      <font-size>10pt</font-size>
      <line-height>1.5</line-height>
      <space-before-desired>3pt</space-before-desired>
      <space-after-desired>3pt</space-after-desired>
      <child name="strong">
        <font-weight>bold</font-weight>
        <font-color>primary</font-color>
      </child>
      <child name="em">
        <font-style>italic</font-style>
      </child>
    </li>

    <blockquote>
      <font-style>italic</font-style>
      <font-size>11.5pt</font-size>
      <line-height>1.5</line-height>
      <font-color>primary</font-color>
      <font-weight>regular</font-weight>
      <border-color>accent</border-color>
      <border-weight-left>3pt</border-weight-left>
      <padding-left>16pt</padding-left>
      <padding-top>4pt</padding-top>
      <padding-bottom>4pt</padding-bottom>
      <space-before-desired>14pt</space-before-desired>
      <space-after-desired>14pt</space-after-desired>
      <hyphens>false</hyphens>
      <text-align>left</text-align>
    </blockquote>

    <a>
      <font-color>accent</font-color>
      <text-decoration>underline</text-decoration>
    </a>

    <img>
      <width>fill</width>
      <space-before-desired>14pt</space-before-desired>
      <space-after-desired>14pt</space-after-desired>
    </img>

    <hr>
      <space-before-desired>8pt</space-before-desired>
      <space-after-desired>8pt</space-after-desired>
      <color>accent</color>
    </hr>

    <table>
      <width>fill</width>
      <space-before-desired>14pt</space-before-desired>
      <space-after-desired>14pt</space-after-desired>
    </table>
    <th>
      <background-color>primary</background-color>
      <font-color>white</font-color>
      <font>Roboto</font>
      <font-weight>bold</font-weight>
      <font-size>9pt</font-size>
      <case>upper</case>
      <tracking>0.05</tracking>
      <text-align>left</text-align>
      <padding>8pt</padding>
      <border-weight>0</border-weight>
    </th>
    <td>
      <font-size>10pt</font-size>
      <font-weight>light</font-weight>
      <padding>8pt</padding>
      <border-weight>0</border-weight>
      <border-bottom-weight>0.5pt</border-bottom-weight>
      <border-color>grey</border-color>
    </td>

    <pre>
      <font>JetBrains Mono</font>
      <font-size>8pt</font-size>
      <font-color>grey</font-color>
      <background-color>primary</background-color>
      <padding>14pt</padding>
      <border-radius>4pt</border-radius>
      <line-height>1.5</line-height>
      <text-align>left</text-align>
      <hyphens>false</hyphens>
      <width>fill</width>
      <space-before-desired>14pt</space-before-desired>
      <space-after-desired>14pt</space-after-desired>
      <child name="code">
        <font-color>grey</font-color>
        <background-color>transparent</background-color>
        <padding>0</padding>
        <font-size>8pt</font-size>
      </child>
    </pre>

    <code>
      <font>JetBrains Mono</font>
      <font-size>9pt</font-size>
      <font-color>primary</font-color>
    </code>

    <alias name="plain-markdown">
      <child name="strong">
        <font-weight>regular</font-weight>
      </child>
      <child name="em">
        <font-style>normal</font-style>
      </child>
    </alias>

    <alias name="coverTitle">
      <text-align>left</text-align>
      <font-size-max>72pt</font-size-max>
      <font-size>fit</font-size>
      <font-color>accent</font-color>
      <child name="strong">
        <font-weight>black</font-weight>
        <font-color>white</font-color>
      </child>
      <child name="b">
        <font-weight>black</font-weight>
        <font-color>white</font-color>
      </child>
    </alias>
  </styles>

  <components>
    <logo>
      <frame border-color="accent" border-weight-left="4pt" padding-left="12pt" width="fill">
        <frame font-size="16pt">
          <markdown inline="true">{{ data.company.name }}</markdown>
        </frame>
        <frame font-size="7pt" tracking="0.1" font-weight="light" case="upper">{{ data.company.tagline }}</frame>
      </frame>
    </logo>

    <header-right width="fill" direction="row" height="0.75cm">
      <frame height="fill" v-align="center" width="fill" padding-left="1.85cm" background-color="grey"
        font-size-max="10pt" font-size-min="8pt" font-size="fit" font-color="text-black" text-align="left"
        h-align="left" overflow="ellipsis" style="@plain-markdown">
        <markdown inline="true">{{ data.title }}</markdown>
      </frame>
      <frame height="fill" v-align="center" width="3.3cm" padding-right="1.85cm" background-color="primary"
        font-size-max="10pt" font-size-min="8pt" font-size="fit" font-color="white" text-align="right" h-align="right">
        <page-number />
      </frame>
    </header-right>
  </components>

  <flows>
    <toc-flow deferred="true">
      <repeat outline="headings" item="h1">
        <frame height="110pt" break="never" space-after-desired="12pt">
          <frame font-size="16pt">
            <value pad="2">{{ h1.page-number }}</value>
          </frame>
          <hr />
          <frame font-size="12pt" case="upper">
            {{ h1.text-content }}
          </frame>
          <frame font-size-max="9pt" font-size-min="8pt" font-size="fit" line-height="1.4" space-before-desired="4pt"
            width="fill" height="fill" text-align="justified" show-if="data.toc.entries[h1.id]" overflow="ellipsis"
            hyphens="true">
            {{ data.toc.entries[h1.id] }}
          </frame>
        </frame>
      </repeat>
    </toc-flow>

    <contents-footer deferred="true">
      <h1 outline="meta" width="60%" font-size-max="26pt" font-size="fit" font-size-min="16pt"
        space-after-desired="16pt" style="@plain-markdown">
        <markdown inline="true">{{ data.title }}</markdown>
      </h1>
      <p font-size="10pt" line-height="1.6">{{ data.toc.footerDescription }}</p>
    </contents-footer>

    <financials>
      <repeat data="data.financials.sections" item="section">
        <h1>{{ section.heading }}</h1>

        <p show-if="section?.description">
          <markdown inline="true">{{ section.description }}</markdown>
        </p>

        <show-if condition="section?.grid">
          <grid columns="2">
            <repeat data="section.plots" item="plot">
              <frame padding="8pt" max-height="9.5cm">
                <h3 show-if="plot?.title">{{ plot.title }}</h3>
                <show-if condition="plot?.type != 'bar'">
                  <visualization data="plot.data">
                    <config>
                      <colors>
                        <repeat data="plot.colors" item="c">
                          <color>{{ c }}</color>
                        </repeat>
                      </colors>
                    </config>
                    <line-plot
                      height="{{ plot?.height ? plot.height : '4cm' }}"
                      width="100%"
                      y-label="{{ plot?.yLabel }}"
                      y-prefix="{{ plot?.yPrefix }}"
                      y-suffix="{{ plot?.ySuffix }}"
                      y-separator="{{ plot?.ySeparator ? 'true' : 'false' }}"
                      y-dp="{{ plot?.yDp }}"
                      fill="{{ plot?.fill ? plot.fill : 'false' }}"
                    />
                  </visualization>
                </show-if>
              </frame>
            </repeat>
          </grid>
        </show-if>

        <show-if condition="section?.grid ? false : true">

        <repeat data="section.plots" item="plot">
          <h3 show-if="plot?.title">{{ plot.title }}</h3>

          <show-if condition="plot?.type != 'bar'">
            <visualization data="plot.data" width="100%" height="{{ plot?.height ? plot.height : '7cm' }}">
              <config>
                <colors>
                  <repeat data="plot.colors" item="c">
                    <color>{{ c }}</color>
                  </repeat>
                </colors>
              </config>
              <line-plot
                height="100%"
                width="100%"
                y-label="{{ plot?.yLabel }}"
                x-label="{{ plot?.xLabel }}"
                y-prefix="{{ plot?.yPrefix }}"
                y-suffix="{{ plot?.ySuffix }}"
                y-separator="{{ plot?.ySeparator ? 'true' : 'false' }}"
                y-dp="{{ plot?.yDp }}"
                fill="{{ plot?.fill ? plot.fill : 'false' }}"
              />
            </visualization>
          </show-if>

          <show-if condition="plot?.type == 'bar'">
            <visualization data="plot.data" width="100%" height="{{ plot?.height ? plot.height : '6cm' }}">
              <config>
                <colors>
                  <repeat data="plot.colors" item="c">
                    <color>{{ c }}</color>
                  </repeat>
                </colors>
              </config>
              <bar-plot
                height="100%"
                width="100%"
                y-label="{{ plot?.yLabel }}"
                x-label="{{ plot?.xLabel }}"
                y-prefix="{{ plot?.yPrefix }}"
                y-suffix="{{ plot?.ySuffix }}"
                y-separator="{{ plot?.ySeparator ? 'true' : 'false' }}"
                y-dp="{{ plot?.yDp }}"
                font-size="8pt"
                show-labels="auto"
              />
            </visualization>
          </show-if>

          <p show-if="plot?.caption">
            <markdown inline="true">{{ plot.caption }}</markdown>
          </p>
        </repeat>
        </show-if>
      </repeat>
    </financials>

    <body type="markdown">
      <![CDATA[
      # Overview

      This document is the public reference for the Papermill modern report template. The template gives you a single, customisable A4 report — a cover page, a contents page, body pages, and a back-matter page — without having to author a Press template from scratch.

      You can use the template by sending a payload to the Papermill PDF endpoint with `template_id=papermill-modern-report`. The payload merges over the defaults shown throughout this document, so you only need to supply the fields you want to change.

      ## What the template gives you

      Every Papermill modern report is built from four kinds of page:

      - A **cover page** with a navy backdrop, a title, an issue date, a subtitle, and an "issued by" block in the lower corner.
      - A **contents page** that lists each top-level heading from the body, with a per-section description and a page reference.
      - The **body pages**, with a running header that shows the report title and the page number on every page.
      - A **back-matter page** with a tagline, a short closing paragraph, and contact details.

      Each section is independently overridable. Change the cover but keep the back matter, replace the body and let the contents page rebuild itself, and so on.

      ## When to use it

      The template suits any situation where you want a single A4 document with a title, a few sections, and clean typography:

      - **Quarterly updates** — internal reports, board packs, investor letters.
      - **Client briefings** — proposals, summaries, condensed analyses.
      - **Internal documentation** — design notes, architecture overviews, post-mortems.

      For other formats, see the broader Papermill template examples at papermill.io.

      ## Typography and palette

      Body copy is set in Lato at 10 point with justified alignment, automatic hyphenation, and a 1.6 line height. Headings are set in Roboto: 40 point at level one, 16 point at level two, and 11 point at level three (uppercased with letter-spacing to read as a section eyebrow).

      The colour palette is a navy primary, a teal accent, near-black for body text, and a soft grey for borders and the running header band. The cover, contents, and back matter all share the same palette.

      # A worked example

      To show how the template behaves under realistic content, this section runs through a short worked example: a quarterly summary that mixes prose, an ordered list, an image, a pull quote, and a metrics table.

      ## Setting the scene

      In *April*, the team launched a new product line that **doubled** the catalogue available to North American distributors. The change opened up three downstream wins:

      1. Existing distributors gained access to a wider product mix without onboarding paperwork.
      2. The sales team had a clearer cross-sell story to lead with.
      3. Inbound enquiries about category coverage dropped sharply.

      ![Example header image](https://examples.papermill.io/driftline/header-coastline.jpeg)

      ## What this looked like in practice

      The first thirty days saw distributor activations rise across all three regions, with the Atlantic corridor leading on volume and the Midwest leading on average order size. New accounts skewed toward the mid-market segment — single-warehouse operators with annual GMV between two and ten million.

      The full breakdown lives in the regional sales review. For a top-line summary, the rest of this section walks through the headline metrics.

      ## Outcomes

      > Active distributor count, measured as the percentage of partner accounts placing at least one order in the rolling four-week window, climbed from 38% to 71% over the four weeks after launch.

      Average time from product enquiry to first order fell from twenty-three days to four. The reduction came almost entirely from removing the catalogue-coverage objection, not from any change to the order pipeline itself. The full numbers, side by side:

      | Metric                       | Q4 2025 | Q1 2026 | Change |
      | :--------------------------- | ------: | ------: | -----: |
      | Active distributors          |     954 |   1,247 |   +30% |
      | Activation rate              |     38% |     71% |  +33pt |
      | Days to first order          |      23 |       4 |   -83% |
      | Catalogue lines available    |   1,420 |   2,860 |  +101% |

      # The complete payload

      To regenerate this report end-to-end, send a Press XML payload that supplies the body Markdown inside a `<flows>` block and any cover, contents, or back-matter overrides inside a `<data>` block. The shape below is the same payload that produces the document you are reading — see *The default data* (next section) for the full data block that fills in the surrounding shell.

      ```xml
      <press>
        <flows>
          <body type="markdown">
            # Overview

            ...body Markdown goes here...

            ## A subsection

            Body copy renders as justified, hyphenated Lato. Lists,
            blockquotes, tables, and images all pick up the template
            styles automatically.
          </body>
        </flows>

        <data type="json">
          { ...see "The default data" below... }
        </data>
      </press>
      ```

      Send it to the Papermill PDF endpoint with `template_id=papermill-modern-report`:

      ```bash
      curl -X POST \
      -H "Content-Type: text/xml" \
      -H "X-API-Key: $PAPERMILL_API_KEY" \
      --data-binary @payload.xml \
      -o report.pdf \
      "https://api.papermill.io/v2/pdf?template_id=papermill-modern-report"
      ```

      A few notes about the body Markdown:

      - Headings at level one (`#`) show up on the contents page; lower-level headings (`##`, `###`) only appear in the body.
      - The contents page reads its per-section descriptions from `toc.entries`, keyed by the slugified body heading. A
      heading of `# My report` looks up `toc.entries["my-report"]`.
      - The fields that contain Markdown — the title, the cover subtitle, the company name, and the back-matter tagline —
      accept inline emphasis. Wrapping a fragment in `**double asterisks**` paints it bold; on the cover title, the bold
      fragment is also recoloured white against the navy backdrop.

      # The default data

      The template ships with the JSON data block below. Whatever you supply in your payload's `<data>` tag merges over these
        defaults at the matching path, so you only need to send the fields you want to change.

        ```json
        {
          "title": "**Sample** Report",
          "issueDate": "Q1 2026",
          "cover": {
            "subtitle": "**Customisable** Report Template"
          },
          "company": {
            "name": "**Papermill**",
            "tagline": "Document generation API"
          },
          "author": {
            "name": "Papermill",
            "address": "Replace with your address",
            "postcode": "Postcode"
          },
          "contact": {
            "website": "www.papermill.io",
            "email": "hello@papermill.io",
            "address": "Replace with your address",
            "postcode": "Postcode"
          },
          "toc": {
            "entries": {
            "overview": "What the template gives you and when to use it.",
            "a-worked-example": "A short quarterly summary in real prose.",
            "the-complete-payload": "The Press XML payload that regenerates this report.",
            "the-default-data": "The JSON data block that fills the cover, contents, and back matter.",
            "output-formats": "Page size and draft watermarking via the request URL."
            },
            "footerDescription": "A worked example of the modern report template, regenerable from the payload inside."
          },
          "backMatter": {
            "tagline": "**Generated** with Papermill",
            "description": "Papermill turns structured data into branded PDFs without the maintenance overhead of templating in
            code."
          }
        }
        ```

        # Output formats

        The template renders to PDF — a single A4 document, paginated to fit the body. Long reports flow over as many
        right-running pages as needed, and the running header repeats on every body page.

        ## Drafts and final renders

        Add `draft=true` to the request URL to render a watermarked draft. Omit it for a final render suitable for
        distribution.
  ]]></body>
  </flows>

  <data type="json">
    {
      "title": "**Sample** Report",
      "issueDate": "Q1 2026",
      "cover": {
        "subtitle": "**Customisable** Report Template"
      },
      "company": {
        "name": "**Papermill**",
        "tagline": "Document generation API"
      },
      "author": {
        "name": "Papermill",
        "address": "Replace with your address",
        "postcode": "Postcode"
      },
      "contact": {
        "website": "www.papermill.io",
        "email": "hello@papermill.io",
        "address": "Replace with your address",
        "postcode": "Postcode"
      },
      "toc": {
        "entries": {
          "overview": "What the template gives you and when to use it.",
          "a-worked-example": "A short quarterly summary in real prose.",
          "the-complete-payload": "The Press XML payload that regenerates this report.",
          "the-default-data": "The JSON data block that fills the cover, contents, and back matter.",
          "output-formats": "Page size and draft watermarking via the request URL."
        },
        "footerDescription": "A worked example of the modern report template, regenerable from the payload inside."
      },
      "backMatter": {
        "tagline": "**Generated** with Papermill",
        "description": "Papermill turns structured data into branded PDFs without the maintenance overhead of templating in code."
      },
      "financials": {
        "sections": []
      }
    }
  </data>
</press>

Generating PDFs

shell
curl -X POST https://api.papermill.io/v2/pdf?template_id=papermill-modern-report \
  -H "Authorization: Bearer $PAPERMILL_API_KEY" \
  -H "Content-Type: text/xml" \
  -o report.pdf \
  --data-binary @- <<EOF
  <insert payload here>
EOF
python
import os
import requests

API_KEY = os.environ["PAPERMILL_API_KEY"]

payload = """
    <payload here>
"""

response = requests.post(
    "https://api.papermill.io/v2/pdf?template_id=papermill-modern-report",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "text/xml",
    },
    data=payload.encode("utf-8"),
)
response.raise_for_status()

with open("report.pdf", "wb") as f:
    f.write(response.content)
javascript
import fs from 'node:fs/promises'

const API_KEY = process.env.PAPERMILL_API_KEY

const payload = `
  <insert payload here>
`

const response = await fetch('https://api.papermill.io/v2/pdf?template_id=papermill-modern-report', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'text/xml',
  },
  body: payload,
})

if (!response.ok) {
  throw new Error(`${response.status} ${response.statusText}`)
}

const buffer = Buffer.from(await response.arrayBuffer())
await fs.writeFile('report.pdf', buffer)

Generating plots

INFO

To generate integrated plots using Papermill, the Premium plan is required

By default, this report has an empty financials flow as no data is set to populate the contents of the flow. We can set the data to populate the financials flow and see the plots using the following payload:

Details
xml
<press>
  <flows>
    <body type="markdown">
      # Q1 2026 at a glance

      Q1 2026 closed as a **recovery quarter**. The packaging change at our largest
      fulfilment partner had stalled outbound shipments through November and
      December; once that cleared in mid-January, demand snapped back faster than
      the pre-disruption trend would have predicted. March 2026 finished at a new
      monthly revenue high, and every channel except the deliberately repriced
      marketplace cohort grew sequentially across the three months.

      The rest of this report tells the quarter through three plots. The first is
      a nine-month run of monthly net revenue from July 2025 through March 2026,
      which makes the late-Q4 trough and the Q1 recovery legible at a glance.
      The second compares Q4 2025 against Q1 2026 across our four sales channels
      side by side; it surfaces both the broad-based growth in direct, partner,
      and inbound revenue and the intentional contraction in marketplace as we
      pulled the long tail of legacy SKUs off the price-sensitive surface.

      The third panel is a small-multiples view of partner-account activation
      rates across the four regions over the last four quarters. Each region has
      run a distinct shape — a steady climb in the Atlantic corridor, a late
      surge in the Midwest after the Chicago distribution centre came online,
      a sharp V-shaped recovery in the Pacific Northwest after a Q3 supplier
      outage, and a slower but steady accumulation in the South. Read together
      with the revenue picture, they make clear that the headline rebound was
      pulled forward by demand we already had, not by a one-off accounting lift.
    </body>
  </flows>

  <data type="json">
    {
      "title": "**Q1 2026** Performance Review",
      "issueDate": "Q1 2026",
      "cover": {
        "subtitle": "**Plots-driven** quarterly summary"
      },
      "company": {
        "name": "**Acme Co**",
        "tagline": "Quarterly performance reporting"
      },
      "author": {
        "name": "Performance team",
        "address": "1 Example Street",
        "postcode": "EX1 2MP"
      },
      "contact": {
        "website": "www.example.com",
        "email": "performance@example.com",
        "address": "1 Example Street",
        "postcode": "EX1 2MP"
      },
      "toc": {
        "entries": {
          "q1-2026-at-a-glance": "Three-plot summary of the quarter: revenue trend, channel mix, and regional activation.",
          "headline-performance": "Monthly net revenue across H2 2025 and Q1 2026, plotted with a currency-formatted y-axis and a fade fill that surfaces the December trough.",
          "revenue-by-channel": "Grouped bar plot comparing Q4 2025 against Q1 2026 across direct, partner, marketplace, and inbound channels.",
          "regional-activation-detail": "Four small-multiple line plots — one per region — laid out as a 2x2 grid for direct shape comparison."
        },
        "footerDescription": "Q1 2026 performance, told through three plots driven entirely by the financials data."
      },
      "backMatter": {
        "tagline": "**Generated** with Papermill",
        "description": "The same payload renders identically every quarter — only the chart data and TOC descriptions change."
      },
      "financials": {
        "sections": [
          {
            "heading": "Headline performance",
            "description": "Net revenue tells a **recovery story**: a steady H2 2025 climb, a sharp end-of-year dip, and a clean three-month rebound across Q1 2026.",
            "plots": [
              {
                "type": "line",
                "data": [
                  ["Month", "Net revenue"],
                  ["Jul 25", 118000],
                  ["Aug 25", 124000],
                  ["Sep 25", 142000],
                  ["Oct 25", 156000],
                  ["Nov 25", 134000],
                  ["Dec 25", 109000],
                  ["Jan 26", 142000],
                  ["Feb 26", 158000],
                  ["Mar 26", 174000]
                ],
                "colors": ["primary"],
                "yLabel": "Net revenue",
                "yPrefix": "£",
                "ySeparator": true,
                "yDp": "0",
                "fill": "fade",
                "caption": "March 2026 closed at **£174k** — a new monthly high and roughly 60% above the December trough."
              }
            ]
          },
          {
            "heading": "Revenue by channel",
            "description": "Channel mix has shifted noticeably quarter-on-quarter. Direct and partner sales both grew double-digits; marketplace contracted as we deliberately repriced the long tail of legacy SKUs.",
            "plots": [
              {
                "type": "bar",
                "data": [
                  ["Channel", "Q4 25", "Q1 26"],
                  ["Direct", 412, 487],
                  ["Partner", 268, 331],
                  ["Marketplace", 184, 156],
                  ["Inbound", 92, 138]
                ],
                "colors": ["primary", "accent"],
                "yLabel": "Revenue",
                "yPrefix": "£",
                "ySuffix": "k",
                "yDp": "0",
                "caption": "Direct and partner up double-digits; marketplace contracted as the legacy SKU cohort was deliberately repriced."
              }
            ]
          },
          {
            "heading": "Regional activation detail",
            "description": "Activation rate — the share of partner accounts placing at least one order in the rolling four-week window — across the four regions over the last four quarters.",
            "grid": true,
            "plots": [
              {
                "type": "line",
                "title": "Atlantic corridor",
                "data": [
                  ["Quarter", "Activation"],
                  ["Q2 25", 38],
                  ["Q3 25", 47],
                  ["Q4 25", 58],
                  ["Q1 26", 71]
                ],
                "colors": ["primary"],
                "ySuffix": "%",
                "yDp": "0",
                "fill": "fade"
              },
              {
                "type": "line",
                "title": "Midwest",
                "data": [
                  ["Quarter", "Activation"],
                  ["Q2 25", 31],
                  ["Q3 25", 34],
                  ["Q4 25", 52],
                  ["Q1 26", 62]
                ],
                "colors": ["primary"],
                "ySuffix": "%",
                "yDp": "0",
                "fill": "fade"
              },
              {
                "type": "line",
                "title": "Pacific Northwest",
                "data": [
                  ["Quarter", "Activation"],
                  ["Q2 25", 28],
                  ["Q3 25", 22],
                  ["Q4 25", 33],
                  ["Q1 26", 54]
                ],
                "colors": ["primary"],
                "ySuffix": "%",
                "yDp": "0",
                "fill": "fade"
              },
              {
                "type": "line",
                "title": "South",
                "data": [
                  ["Quarter", "Activation"],
                  ["Q2 25", 26],
                  ["Q3 25", 32],
                  ["Q4 25", 38],
                  ["Q1 26", 46]
                ],
                "colors": ["primary"],
                "ySuffix": "%",
                "yDp": "0",
                "fill": "fade"
              }
            ]
          }
        ]
      }
    }
  </data>
</press>

Adapt this Template

  • Changing the financial data in the JSON will change the visualisations. To modify their appearance, see the documentation on visualisations.
  • Some of the text, such as the company introduction, is editable from within the JSON data. Text with more complex formatting requirements - for example, potentially running over multiple pages - is in flows.