Appearance
Modern Report
A modern report template, pre-populated with data for a PropTech report but widely reusable for modern reporting.
![]()
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">
<
## 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>
EOFpython
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.