Appearance
Content Flows
Flows are Press's way of separating content from layout. A flow is a named container of content that can be painted into one or more frames across one or more pages. This separation is what makes Press templates truly reusable.
Defining Flows
Flows live inside the <flows> top-level element. Each flow has a tag name that becomes its identifier:
xml
<flows>
<body>
<h1>Annual Report</h1>
<p>This year we achieved significant milestones...</p>
</body>
<sidebar>
<h3>Key Figures</h3>
<p>Revenue: $2.4M</p>
<p>Growth: 18%</p>
</sidebar>
</flows>Referencing Flows
Inside a page definition, use <flow name="..." /> to place content from a flow:
xml
<document format="A4" page-margin="2cm">
<page>
<flow name="body" />
</page>
</document>Press fills the frame containing the flow reference with as much content as will fit.
Multi-Column Layouts
Reference the same flow multiple times to create columns. Press fills the first frame, then continues into the second:
xml
<page>
<frame direction="row" width="100%" height="100%">
<frame width="48%">
<flow name="body" />
</frame>
<frame width="4%" />
<frame width="48%">
<flow name="body" />
</frame>
</frame>
</page>The body content flows from the left column into the right column automatically.
Flowing Across Pages
To continue a flow across multiple pages, use <repeat flow="...">:
xml
<document format="A4" page-margin="2cm">
<repeat flow="body">
<page>
<flow name="body" />
</page>
</repeat>
</document>Press keeps generating pages and filling them with body content until the flow is fully consumed.
Alternating Page Layouts
Place multiple pages inside a repeat for alternating designs:
xml
<repeat flow="body">
<page>
<frame margin-bottom="0.5cm" font-color="grey" text-align="right">
{{ data.author }}
</frame>
<flow name="body" />
</page>
<page>
<frame margin-bottom="0.5cm" font-color="grey">
{{ data.title }}
</frame>
<flow name="body" />
</page>
</repeat>This alternates between a right-aligned author header and a left-aligned title header, similar to how printed books alternate left and right pages.
Markdown Flows
Set type="markdown" on a flow to write content in markdown instead of Press XML:
xml
<flows>
<body type="markdown">
# Quarterly Review
Our team delivered several key improvements this quarter:
- Reduced processing time by **40%**
- Launched three new product features
- Expanded to two new markets
## Financial Summary
Revenue grew steadily across all segments. The operations
team maintained costs within budget while scaling output.
</body>
</flows>Press automatically strips leading indentation from markdown content.
Markdown from the payload
A markdown flow can take its content from an expression, so whole sections arrive as strings in the payload rather than as Press markup:
xml
<data type="json">
{ "summary": "## This month\n\nWe shipped **three** things:\n\n- one\n- two" }
</data>
<flows>
<summary type="markdown">{{ data.summary }}</summary>
</flows>The expression has to sit directly in the flow body. The same expression inside <repeat> or <show-if> renders its markdown as literal characters — - item keeps its hyphen, ## Heading its hashes — and the document still renders, with no warning. Use <markdown inline="true"> for each item instead, which covers bold, italics and links but not headings or lists.
What a payload string can carry
A payload string rendered through a markdown flow is read as Markdown, not as Press. A tag in the string is typeset as the characters it is written with — <h1> prints as <h1>, and the Markdown around it still renders, so a paragraph under a stray <div> keeps its bold. This is about copy that arrives as a string: markup you write into the flow yourself is applied as markup, which is Mixing Markdown and Press below.
Validation reports a tag in the string as a warning, so copy you did not author can be checked before you render it. Three further consequences worth knowing. A line break comes from a newline in the string, so <br /> prints rather than breaking one. A closed <!-- … --> is dropped, as a comment is written to be. And a long tag with no spaces in it — an <img src="data:image/png;base64,…">, the shape a model emits for a chart — is one unbreakable word, so once it is longer than the line it has to fit, Papermill rejects the document with message Element doesn't contain enough space for text; strip data URIs before sending.
An expression is different: {{ … }} resolves against the payload like any other expression, so {{ data.secret }} written into the copy renders the value.
Untrusted copy can also fail the render rather than leak: an expression naming a missing field, an unsupported operator, a wide table, a skipped heading level, or an image whose URL Papermill cannot reach —  and the ![alt][ref] form both fetch. Each names a position in the template rather than in the payload string.
Mixing Markdown and Press
You can embed Press elements directly inside a markdown flow when you need more control:
xml
<flows>
<body type="markdown">
# Project Status
The project is on track for delivery in Q2.
<frame direction="row" space-before-desired="12pt" space-after-desired="12pt">
<frame width="fill" background-color="#f0fdf4" padding="12pt" border-radius="4pt">
<h3 font-color="#16a34a">Completed</h3>
<p>12 tasks</p>
</frame>
<frame width="8pt" />
<frame width="fill" background-color="#fef2f2" padding="12pt" border-radius="4pt">
<h3 font-color="#dc2626">Remaining</h3>
<p>4 tasks</p>
</frame>
</frame>
## Next Steps
The team will focus on integration testing next sprint.
</body>
</flows>Flow Groups
Use <group> to organise a flow into named sections. Groups are essential for documents with repeating section structures:
xml
<flows>
<group name="chapters">
<introduction type="markdown">
<data type="json">
{ "chapter-title": "Introduction", "chapter-number": 1 }
</data>
This report examines market conditions across three regions...
</introduction>
<methodology type="markdown">
<data type="json">
{ "chapter-title": "Methodology", "chapter-number": 2 }
</data>
Data was collected from public records spanning 2020 to 2025...
</methodology>
<findings type="markdown">
<data type="json">
{ "chapter-title": "Findings", "chapter-number": 3 }
</data>
The analysis revealed consistent growth in urban centres...
</findings>
</group>
</flows>Then iterate over the groups in your document:
xml
<document format="A4" page-margin="2cm">
<repeat group="chapters" item="chapter">
<page>
<h1>{{ chapter.data['chapter-number'] }}. {{ chapter.data['chapter-title'] }}</h1>
<flow name="chapter" />
</page>
</repeat>
</document>Flow Data
Each flow (or group member) can include its own <data> element. This data is accessible within the flow and on any page that renders it:
xml
<flows>
<group name="properties">
<unit-a>
<data type="json">
{ "address": "14 Maple Street", "bedrooms": 3, "price": "£425,000" }
</data>
<p>A charming three-bedroom property with garden views...</p>
</unit-a>
<unit-b>
<data type="json">
{ "address": "7 Oak Avenue", "bedrooms": 2, "price": "£310,000" }
</data>
<p>Modern two-bedroom flat in a converted Victorian terrace...</p>
</unit-b>
</group>
</flows>On the page, access the flow's data through the item variable:
xml
<repeat group="properties" item="property">
<page>
<h1>{{ property.data.address }}</h1>
<p>{{ property.data.bedrooms }} bedrooms - {{ property.data.price }}</p>
<flow name="property" />
</page>
</repeat>Anonymous Flows
You can define a flow directly inside a page without giving it a name. This is useful for one-off content:
xml
<page>
<flow>
<h1>Disclaimer</h1>
<p>This document is for informational purposes only...</p>
</flow>
</page>It's also possible to give anonymous flows a name and have the content repeat over multiple pages. This is especially useful for things like long, multi-page table of contents:
xml
<!-- table of contents -->
<page flow="toc" repeat="true" deferred="true">
<!-- in anon flow so heading only appears on first page as flow is then empty -->
<flow name="toc-heading">
<h1 outline="meta" padding-bottom="0.7cm" font-color="primary">Table of Contents</h1>
</flow>
<frame>
<!--
another anonymous flow to allow table of contents
to be more than one page long
-->
<flow name="toc">
<repeat outline="heading" item="h1">
<!-- render h1s -->
{{ h1.index }}. {{ h1['text-content'] }} - Page <value pad="2">{{ h1['page-number'] }}</value>
</repeat>
</flow>
</frame>
</page>