Skip to main content

Templates

Introduction

When converting HTML to PDF or a screenshot, the source parameter accepts one of three input types:

{
"source": "<html><body><h1>Hello World</h1></body></html>"
}
{
"source": "https://example.com/my-page"
}
{
"source": "[template:a1b-c2d]",
"templateData": {
"customer_name": "Jane Smith",
"invoice_total": 1320
}
}

Raw HTML and URL are straightforward — you provide the content directly in every API call. Templates take a different approach: the HTML lives on the server, and your API call only sends the dynamic data.

When to use templates

Reusable document designs
Define the layout once — invoice, report, ticket, or any other document type — and generate as many instances as you need from it.
Dynamic data at render time
Inject customer names, line items, dates, and totals into the HTML using Liquid syntax, without rebuilding HTML in your application on every call.
Consistent branding
Maintain a single source of truth for headers, footers, and styling across all generated documents.
Smaller API payloads
Send only the variable data instead of the entire HTML document on every request.

Default vs User Templates

PolyDoc provides two categories of templates:

Default (System) TemplatesUser Templates
Provided byPolyDocYou
EditableNo (read-only)Yes
Usable in API callsNoYes — via short ID
StabilityMay be updated or removedStable — you control changes

Default templates are starting points — professionally designed examples such as invoices, bank statements, reports, and tickets. They exist so you can see what is possible and get a head start, but they are maintained by PolyDoc and may change at any time.

User templates are your own. Each one receives a unique short ID (e.g. a1b-c2d) that you reference in API calls. You have full control over the content, styles, header, footer, and default options.

Built-in template examples

Sample exports from the default templates (placeholder data). Previews show the first page of each PDF; click to open the full file.

Invoice — first page preview
InvoicePDF
Report — first page preview
ReportPDF
Ticket — first page preview
TicketPDF
Bank statement — first page preview
Bank statementPDF
Email bannerScreenshot

Duplicating and creating templates

You can create a user template in two ways:

  • From scratch — click New Template in the Dashboard and build your own HTML, styles, header and footer.
  • Duplicate a default — pick a default template and click Duplicate. This copies the content, styles, header, footer, and default options into a new user template that you can customize.

Template Structure

Every template consists of up to four parts:

PartPurposeApplies to
ContentMain HTML body of the documentPDF + Screenshot
HeaderHTML shown at the top of every PDF pagePDF only
FooterHTML shown at the bottom of every PDF pagePDF only
StylesCSS applied to the main contentPDF + Screenshot

Additionally, each template can define Default Options — a JSON object with default API parameters (margins, page format, viewport size, etc.). These are merged with the options you send in each request, where request-level values take priority.

Header and Footer (PDF only)

Headers and footers are rendered by the browser's print engine in a separate context from the main content. This has a few important implications:

  • They do not share CSS with the main content. Any styles you need in the header or footer must be defined as inline <style> blocks within the header/footer HTML itself.
  • Header and footer do share styles with each other — a <style> block in the header is also available in the footer, and vice versa.
  • Header/footer styles are not subject to Content Security Policy (CSP) restrictions, so inline styles always work.
  • When using headers or footers, you need to account for their height in your page margins. For example, an invoice header of approximately 2.5 inches requires setting "layout": { "margin": { "top": "2.8in" } } to prevent content from overlapping.

Examples

This header shows the company logo, address, invoice details, and page numbers. Note the inline <style> block — required because the header does not inherit styles from the main content.

<html>
<head>
<style>
body { font-family: "Open Sans", sans-serif; }
* { font-size: 16px; }
.header { margin: 1cm 1cm 0; }
.flex { display: flex; }
.justify-between { justify-content: space-between; }
.text-xs, .text-xs * { font-size: 0.75rem; }
/* ... more utility classes as needed ... */
</style>
</head>
<body>
<div class="header flex justify-between">
<div>
<img src="data:image/svg+xml;base64,..." />
<div>Acme Inc.</div>
<div class="text-xs">123 Main St, Anytown, USA</div>
</div>
<div>
<div>Invoice</div>
<div class="text-xs">
Page: <span class="pageNumber"></span>
of <span class="totalPages"></span>
</div>
</div>
</div>
</body>
</html>

A simple footer strip with company info. It automatically appears at the bottom of every page.

<html>
<body>
<div style="display: flex; justify-content: center;
border-top: 1px solid #e0e0e0;
padding-top: 0.75rem; margin-top: 0.75rem;
font-size: 0.625rem; color: #99a1af; gap: 0.5rem;">
Acme Inc. · 123 Main St · Anytown, USA
<div style="border-left: 1px solid #99a1af;
border-right: 1px solid #99a1af;
padding: 0 0.5rem; margin: 0 0.5rem;">
<a href="https://www.acme-inc.com">www.acme-inc.com</a>
</div>
VAT: 1234567890
</div>
</body>
</html>

Using Templates via API

To use a template, set the source parameter to a template reference in the format [template:xxx-yyy], where xxx-yyy is the template's short ID. You can find the short ID on the Templates page of the dashboard.

Pass dynamic values through the templateData object. Every key in templateData becomes available as a Liquid variable inside the template.

{
"source": "[template:a1b-c2d]",
"templateData": {
"invoice_number": "INV-2026-042",
"invoice_date": "2026-03-15",
"invoice_due_date": "2026-04-14",
"customer_name": "Jane Smith",
"customer_street": "789 Oak Avenue",
"customer_city": "Springfield",
"customer_country": "US",
"invoice_subtotal": 1500,
"invoice_tax_rate": 0.08,
"invoice_tax_amount": 120,
"invoice_total": 1620,
"items": [
{
"quantity": 5,
"name": "Consulting Hours",
"description": "Technical architecture review",
"price": 200
},
{
"quantity": 1,
"name": "Project Setup",
"price": 500
}
]
}
}

Option merging

Templates can store default options such as page margins, format, or viewport dimensions. When you make an API request, the options are merged in this order:

  1. Template defaults are applied first (e.g., margins defined in the template's Options tab).
  2. Request-level options override any matching template defaults.

For example, if a template defines "layout": { "margin": { "top": "2.8in" } } and your request also specifies layout.margin.top, the request value wins. Options not specified in the request fall back to the template defaults.

Liquid Templating

Templates use Liquid as their templating language. Every key in the templateData object you send with the API request becomes a variable you can reference in the template's content, header, and footer.

Output a value with double curly braces. Dot notation accesses nested properties.

<!-- Simple variable -->
<h1>Invoice {{ invoice_number }}</h1>

<!-- Nested property -->
<td>{{ item.name }}</td>

<!-- Combining variables -->
<div>{{ customer_name }}, {{ customer_city }}</div>

Iterate over arrays with {% for %}. This is the core pattern for rendering line items, transactions, or any list of records. The example below — from the invoice template — loops over an items array and renders a table row per line item, including an optional description and computed total.

{% for item in items %}
<tr>
<td>{{ item.quantity }}</td>
<td>
<div>{{ item.name }}</div>
{% if item.description %}
<div class="text-gray-400">{{ item.description }}</div>
{% endif %}
</td>
<td>${{ item.price | round: 2 }}</td>
<td>${{ item.price | times: item.quantity | round: 2 }}</td>
</tr>
{% endfor %}

Use {% if %} to show or hide content based on the data. You can also check string content with contains.

<!-- Show a field only when present -->
{% if item.description %}
<div class="text-gray-400">{{ item.description }}</div>
{% endif %}

<!-- Check string content (e.g. negative amounts in a bank statement) -->
{% if transaction.amount contains "-" %}
<span class="text-red-500">{{ transaction.amount }}</span>
{% else %}
<span class="text-green-500">+{{ transaction.amount }}</span>
{% endif %}

Filters transform values using the pipe (|) operator. Multiple filters can be chained.

<!-- Math: round, multiply -->
${{ item.price | round: 2 }}
${{ item.price | times: item.quantity | round: 2 }}
Tax ({{ invoice_tax_rate | times: 100 }}%)

<!-- Dates: format a date string -->
{{ invoice_date | date: "%B %d, %Y" }}
{{- "- Output: March 15, 2026" }}

{{ transaction.booking_date | date: "%m/%d" }}
{{- "- Output: 03/15" }}

<!-- Default: fallback when a value is missing -->
{{ notes | default: "No additional notes" }}

Tips and Best Practices