Skip to main content

Templates

This guide covers PDFBolt template concepts, common use cases, the end-to-end workflow, and a complete invoice example. It's the starting point if you're new to templates – for the visual designer and Dashboard workflow, see the template management guide.

What are Templates?

Templates are reusable HTML layouts with placeholder variables. You design the layout once, then send a templateId and JSON data to generate a PDF for each set of inputs – without resending the full HTML every time.

Template Overview

Instead of sending HTML on every request, templates separate design from data:

  1. Template Definition – Create an HTML layout with placeholders (variables) using Handlebars syntax:

    • Use AI Template Generation – just describe what you need or attach reference files.
    • Choose from our professionally designed gallery of ready-to-use templates.
    • Or design your own template from scratch using our visual designer.
  2. Data Injection – Send only the template ID and JSON data payload to our API to generate personalized PDFs:

    • PDFBolt automatically merges your JSON data with the template.
    • You can call the API from any language – Node.js, Python, PHP, Java, C#, Go, Rust – or with cURL.
Data Privacy

Template data is not stored – it's only used for PDF generation and immediately discarded.

Your data remains private and secure.


PDFBolt uses Handlebars as its templating engine. Handlebars extends HTML with:

  • Variable Substitution{{customer_name}} gets replaced with actual customer data.
  • Conditional Logic{{#if is_paid}}...{{/if}} shows content based on conditions.
  • Loops & Iteration{{#each items}}...{{/each}} repeats content for arrays.
  • Nested Objects – Access deep data structures like {{customer.address.city}}.
Handlebars Syntax

Learn more about Handlebars templating syntax in the official Handlebars documentation.

Need Another Template Engine?

We currently support Handlebars. If you need a different engine, contact us at contact@pdfbolt.com, and we'll consider adding it based on your needs.

Simple Example: Template Transformation

This basic example shows the core concept – see the full invoice example below.

Template HTML (with Handlebars):

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invoice #{{invoice_number}}</title>
<style>
.invoice { margin: 40px; }
.status.paid { color: green; }
.status.pending { color: orange; }
</style>
</head>
<body>
<div class="invoice">
<h1>Invoice #{{invoice_number}}</h1>
<p>Dear {{customer.name}},</p>
<div class="items">
{{#each items}}
<p class="item">
{{name}}: ${{price}}
</p>
{{/each}}
</div>
{{#if is_paid}}
<p class="status paid">PAID</p>
{{else}}
<p class="status pending">PENDING</p>
{{/if}}
<p><strong>Total: ${{total}}</strong></p>
</div>
</body>
</html>

JSON Data:

{
"invoice_number": "INV-001",
"customer": {"name": "John Doe"},
"items": [
{"name": "Premium Service", "price": "199.99"},
{"name": "Setup Fee", "price": "99.99"}
],
"is_paid": true,
"total": "299.98"
}

Final PDF Result:

Generated PDF Invoice

Key Benefits

Templates separate design from data, so you maintain one HTML layout instead of generating it on every request.

Simplified API Calls

  • Cleaner code – Send templateId and templateData instead of full HTML on every request.
  • Easier debugging – Template and data live in separate places.

Consistent Design

  • Single source of truth – Publish an updated version once; future API-generated PDFs use that version.
  • Built-in version control – Roll back to a previous version if needed. Drafts do not affect API output until you publish them.

Faster Development

  • AI-Powered CreationGenerate templates with AI from a description or reference files.
  • Ready-to-Use Gallery – Start with ready-made designs for invoices, reports, certificates, resumes, and more.
  • Visual Designer – Build and test templates with real-time preview.
  • Developer-friendly – Handlebars syntax is familiar to most developers, plus ready-to-use code snippets.

Flexible Data Handling

  • Nested data – Handlebars handles arrays and nested objects ({{customer.address.city}}, {{#each line_items}}).
  • Conditional logic – Show or hide content based on data values ({{#if}}, {{#unless}}).

Team Efficiency

  • Team collaboration – Manage templates across your organization.
  • No infrastructure overhead – Handle growing document volume through the API; PDFBolt manages the rendering infrastructure.

Common Use Cases

Templates fit any document workflow that combines a fixed layout with variable data – from billing automation to certificates.

Business Operations

Invoices, Receipts, Purchase Orders, Quotes, Statements

Analytics & Reporting

Financial reports, KPI dashboards, Performance reviews, Executive summaries

Education & Human Resources

Certificates, Course completion, Employee handbooks, Offer letters, Performance evaluations

Customer Communications

Personalized letters, Product catalogs, Support tickets, Shipping labels

Legal & Compliance

Contracts, Agreements, Policy documents, Audit reports

Sales & Marketing

Sales proposals, Product sheets, Marketing brochures, Event materials

How Templates Work

The template workflow has four steps:

1. Template Creation

  • Design your HTML layout using our visual designer, generate with AI from your description and attached files, or choose from our template gallery.
  • Add Handlebars variables ({{variable_name}}) for dynamic content insertion.
  • Include conditional logic ({{#if}}) and loops ({{#each}}) for complex document structures.
  • Preview with sample data using quick HTML preview and generate real PDFs to verify the final output.
  • You can also duplicate templates for variations and save drafts automatically.
Template Designer - Code Editor with Live Preview

2. Template Publishing

  • Publish your template to make it available via API endpoints.
  • Get your unique template ID for seamless integration.
  • Built-in version control allows you to roll back changes safely.
Template Designer - Publish Version Dialog

Learn More

See more about template publishing.

3. PDF Generation

  • Copy ready-to-use code snippets for Node.js, Python, PHP, Java, C#, Go, and Rust from the Dashboard.
  • Send an API request with your templateId and templateData JSON.
  • PDFBolt merges your data with the template and returns the rendered PDF.
Template Designer - API Code Integration Examples

4. Receive Results

  • Receive a PDF ready for download, email, or storage.
Generated Invoice PDF

Learn More

See the full list of PDF customization options.

Before You Call the API

A few rules that determine whether your request succeeds:

  • Templates must be published before they can be called via the API.
  • templateId must be the UUID of the published template (copy it from the Dashboard).
  • When templateId is provided, templateData is required.
  • templateData must be a JSON object (e.g., {"key": "value"}) – not an array, string, or number.
  • Send exactly one of: html, url, or templateId per request.
  • Request-level PDF parameters (e.g., format, landscape, printBackground) override the template's PDF Options.
  • Templates work with all conversion endpoints: /v1/direct, /v1/sync, and /v1/async.

Complete Example: From Template to PDF

A complete invoice example, end-to-end – HTML template, JSON payload, cURL request, and rendered PDF:

1. Create your HTML template with Handlebars variables and logic (or generate with AI / choose from template gallery):

Example Template Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Invoice - {{invoice_number}}</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Dancing+Script:wght@700&display=swap');

:root {
/* Brand Colors */
--accent-color: #b8577e;

/* Text Colors */
--text-primary: #333;
--text-secondary: #555;
--text-white: #fff;
--text-muted: #777;

/* Background Colors */
--bg-primary: #ffffff;
--bg-secondary: #fafafa;
--bg-accent: #f9f9f9;

/* Border Colors */
--border-light: #e9e9e9;
--border-medium: #ddd;
--border-dark: #ccc;

/* Font Sizes */
--font-size-xs: 12px;
--font-size-sm: 13px;
--font-size-base: 14px;
--font-size-lg: 16px;
--font-size-xl: 18px;
--font-size-2xl: 20px;
--font-size-3xl: 24px;
--font-size-4xl: 54px;
--font-size-5xl: 72px;
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.7;
font-size: var(--font-size-base);
}

.invoice-container {
background: var(--bg-primary);
padding: 55px;
}

.invoice-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 25px;
gap: 20px;
}

.company-section {
display: flex;
flex-direction: column;
align-items: flex-start;
}

.company-logo {
max-width: 80px;
height: auto;
object-fit: contain;
}

.company-name {
margin-top: 8px;
font-size: var(--font-size-2xl);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 2px;
}

.invoice-title-section {
text-align: right;
}

.invoice-title,
.thank-you-message {
font-family: 'Dancing Script', cursive;
color: var(--accent-color);
font-weight: 700;
line-height: 1;
}

.invoice-title {
font-size: var(--font-size-5xl);
margin-bottom: 10px;
}

.invoice-meta,
.billing-details,
.payment-info-item,
.total-label {
font-size: var(--font-size-base);
color: var(--text-secondary);
}

.invoice-meta-label,
.payment-info-label,
.billing-name,
.total-amount {
font-weight: 600;
color: var(--text-primary);
}

.billing-section,
.summary-section {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
gap: 30px;
flex-wrap: wrap;
}

.billing-block {
padding: 0 8px;
flex: 1;
color: var(--text-secondary);
}

.billing-title,
.payment-info-title {
color: var(--accent-color);
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}

.invoice-meta,
.billing-name,
.payment-info-item {
margin-bottom: 4px;
}

.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
color: var(--text-secondary);
}

.table-header {
background: var(--accent-color);
color: var(--text-white);
}

.items-table th {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}

.items-table th,
.items-table td {
padding: 10px 14px;
font-size: var(--font-size-base);
border-bottom: 1px solid var(--border-medium);
text-align: right;
}

.items-table th:first-child,
.items-table td:first-child {
text-align: left;
}

.payment-info-section {
flex: 1;
margin-top: 12px;
}

.payment-info-section,
.totals-section {
width: 48%;
}

.total-row {
display: flex;
justify-content: space-between;
padding: 10px 4px;
font-size: var(--font-size-base);
align-items: center;
border-bottom: 1px solid var(--border-medium);
}

.total-row-final {
font-weight: 700;
color: var(--accent-color);
border-top: 1.5px solid var(--accent-color);
font-size: var(--font-size-lg);
}

.totals-section .total-row:last-child {
border-bottom: none;
}

.total-row-final .total-amount {
color: var(--accent-color);
}

.thank-you-section {
text-align: center;
margin: 20px 0;
}

.thank-you-message {
font-size: var(--font-size-4xl);
}

@page {
margin: 1.5cm;
}

@media print {
.invoice-container {
padding: 0;
}

.invoice-header,
.billing-section,
.summary-section,
.items-table tr {
page-break-inside: avoid;
}
}
</style>
</head>
<body>
<div class="invoice-container">
<!-- Header section - company logo, name, and invoice details -->
<div class="invoice-header">
<div class="company-section">
{{#if company_logo_url}}
<img src="{{company_logo_url}}" alt="{{company_name}} Logo" class="company-logo"/>
{{/if}}
<div class="company-name">{{company_name}}</div>
</div>
<div class="invoice-title-section">
<h1 class="invoice-title">Invoice</h1>
<div class="invoice-meta">
<p><span class="invoice-meta-label">Invoice No:</span> {{invoice_number}}</p>
<p><span class="invoice-meta-label">Issue Date:</span> {{issue_date}}</p>
{{#if due_date}}
<p><span class="invoice-meta-label">Due Date:</span> {{due_date}}</p>
{{/if}}
</div>
</div>
</div>

<!-- Billing section -->
<div class="billing-section">
<div class="billing-block">
<h2 class="billing-title">Billed from</h2>
<p class="billing-name">{{company_name}}</p>
<div class="billing-details">
{{#if company_address_street}}
<p>{{company_address_street}}</p>
{{/if}} {{#if company_address_line2}}
<p>{{company_address_line2}}</p>
{{/if}}{{#if company_city}}
<p>
{{company_city}}{{#if company_state}}, {{company_state}}{{/if}}{{#if company_postal_code}}
{{company_postal_code}}{{/if}}{{#if company_country}}, {{company_country}}{{/if}}
</p>
{{/if}} {{#if company_email}}
<p>{{company_email}}</p>
{{/if}} {{#if company_phone}}
<p>{{company_phone}}</p>
{{/if}} {{#if company_tax_id}}
<p>Tax ID: {{company_tax_id}}</p>
{{/if}}
</div>
</div>

<div class="billing-block">
<h2 class="billing-title">Billed to</h2>
<p class="billing-name">{{client_name}}</p>
<div class="billing-details">
{{#if client_address_street}}
<p>{{client_address_street}}</p>
{{/if}} {{#if client_address_line2}}
<p>{{client_address_line2}}</p>
{{/if}}{{#if client_city}}
<p>
{{client_city}}{{#if client_state}}, {{client_state}}{{/if}}{{#if client_postal_code}}
{{client_postal_code}}{{/if}}{{#if client_country}}, {{client_country}}{{/if}}
</p>
{{/if}} {{#if client_email}}
<p>{{client_email}}</p>
{{/if}} {{#if client_phone}}
<p>{{client_phone}}</p>
{{/if}} {{#if client_tax_id}}
<p>Tax ID: {{client_tax_id}}</p>
{{/if}}
</div>
</div>
</div>

<!-- Products/services table -->
<table class="items-table">
<thead class="table-header">
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{{#each line_items}}
<tr>
<td class="item-description">{{this.description}}</td>
<td>{{this.quantity}}</td>
<td>{{../currency_symbol}}{{this.unit_price}}</td>
<td>{{../currency_symbol}}{{this.total_amount}}</td>
</tr>
{{/each}}
</tbody>
</table>

<!-- Payment Details -->
<div class="summary-section">
<div class="payment-info-section">
<h3 class="payment-info-title">Payment Information</h3>
{{#if payment_info.bank_name}}
<div class="payment-info-item">
<span class="payment-info-label">Bank Name:</span> {{payment_info.bank_name}}
</div>
{{/if}} {{#if payment_info.account_number}}
<div class="payment-info-item">
<span class="payment-info-label">Account No:</span> {{payment_info.account_number}}
</div>
{{/if}} {{#if payment_info.routing_number}}
<div class="payment-info-item">
<span class="payment-info-label">Routing:</span> {{payment_info.routing_number}}
</div>
{{/if}} {{#if payment_info.payment_terms}}
<div class="payment-info-item">
<span class="payment-info-label">Terms:</span> {{payment_info.payment_terms}}
</div>
{{/if}}
</div>

<!-- Invoice totals and calculations -->
<div class="totals-section">
<div class="total-row">
<span class="total-label">Subtotal</span>
<span class="total-amount">{{currency_symbol}}{{subtotal_amount}}</span>
</div>
{{#if discount_amount}}
<div class="total-row">
<span class="total-label">Discount{{#if discount_percentage}} ({{discount_percentage}}%){{/if}}</span>
<span class="total-amount">-{{currency_symbol}}{{discount_amount}}</span>
</div>
{{/if}} {{#if tax_amount}}
<div class="total-row">
<span class="total-label">Tax{{#if tax_percentage}} ({{tax_percentage}}%){{/if}}</span>
<span class="total-amount">{{currency_symbol}}{{tax_amount}}</span>
</div>
{{/if}}
<div class="total-row total-row-final">
<span class="total-label">Total Amount</span>
<span class="total-amount">{{currency_symbol}}{{total_amount}}</span>
</div>
</div>
</div>

<div class="thank-you-section">
<h3 class="thank-you-message">Thank you!</h3>
</div>
</div>
</body>
</html>

2. Send your API request with template ID and data (cURL example):

Template ID format

Replace YOUR_TEMPLATE_UUID with the ID of your published template.

Example cURL Request
curl 'https://api.pdfbolt.com/v1/direct' \
-H 'API-KEY: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-H 'Content-Type: application/json' \
-d '{
"templateId": "YOUR_TEMPLATE_UUID",
"templateData": {
"invoice_number": "INV-2026-014",
"issue_date": "June 13, 2026",
"due_date": "June 27, 2026",
"company_name": "Pink Brand Studio",
"company_email": "pink@example.com",
"company_phone": "+1 (555) 222-3344",
"company_address_street": "22 Rose Garden Lane",
"company_city": "Blushville",
"company_state": "CA",
"company_postal_code": "90210",
"company_country": "USA",
"company_tax_id": "PB-2026-001",
"company_logo_url": "https://img.pdfbolt.com/business-logo-template.png",
"client_name": "Luxe Beauty Co.",
"client_email": "contact@example.com",
"client_phone": "+1 (555) 667-8899",
"client_address_street": "88 Glamour Ave",
"client_city": "Glowtown",
"client_state": "NY",
"client_postal_code": "10001",
"client_country": "USA",
"client_tax_id": "LB-9988",
"line_items": [
{"description": "Brand Identity Package", "quantity": 1, "unit_price": "600.00", "total_amount": "600.00"},
{"description": "Custom Instagram Templates", "quantity": 1, "unit_price": "200.00", "total_amount": "200.00"},
{"description": "Product Photography Session", "quantity": 1, "unit_price": "350.00", "total_amount": "350.00"},
{"description": "E-commerce Banner Design", "quantity": 2, "unit_price": "75.00", "total_amount": "150.00"},
{"description": "Email Newsletter Template", "quantity": 1, "unit_price": "120.00", "total_amount": "120.00"}
],
"payment_info": {
"bank_name": "Example Bank",
"account_number": "1234567890123456",
"routing_number": "110099001",
"payment_terms": "Due in 14 days"
},
"currency_symbol": "$",
"subtotal_amount": "1420.00",
"discount_percentage": "10",
"discount_amount": "142.00",
"tax_percentage": "20",
"tax_amount": "255.60",
"total_amount": "1533.60"
}
}' \
--output invoice.pdf

3. Receive your generated PDF:

Generated Invoice PDF

Generated invoice PDF with dynamic data and branded styling.


Troubleshooting

If your request returns an error or the rendered PDF doesn't look right, check these common causes:

ProblemCause / Fix
templateId is not a valid UUIDCopy the UUID from the Dashboard – don't use a custom name like my-template.
Template with ID ... has no active versionPublish the draft version of the template. Learn more.
'templateData' must also be providedWhen using templateId, include templateData as a JSON object in the same request.
Variables show as blank in the PDFField names in templateData don't match Handlebars placeholders such as {{customer_name}} – check spelling and case.
Images or fonts missing in PDFAssets must be reachable by PDFBolt's renderer (no localhost). For slow-loading assets, use waitUntil: "networkidle".
JavaScript-rendered content missingUse waitUntil: "networkidle" or waitForFunction.
Header/footer variables don't workUse Chromium placeholder classes, not Handlebars variables. See headerTemplate and footerTemplate.

Templates FAQ

Do I need coding knowledge to use Templates?

No. You can:

  • Use AI Template Generation to create templates from descriptions or reference files – no coding required.
  • Start with ready-made templates from our gallery and customize them for your brand.
  • Use our visual designer with real-time preview.
  • Handlebars syntax is simple – use {{variable_name}} for data insertion.
Can I modify published templates?

Yes. Edit templates to create new drafts, preview changes, and publish new versions when ready. Built-in version control lets you compare changes and roll back safely.

What happens to my template data during PDF generation?

Template data is not stored – it's only used for PDF generation and immediately discarded. Your data remains private and secure.

Can I use custom fonts in my templates?

Yes. Import custom fonts via Google Fonts or any external font URL directly in your template. For example, using CSS @import:

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');

Or using a <link> tag in your template's <head>:

<link href="https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600;700&display=swap" rel="stylesheet" />
Can I pass HTML content in template data?

Yes. By default, Handlebars escapes HTML in variables for security. To render raw HTML from your data, use triple curly braces {{{variable}}} instead of double {{variable}}. For example:

{
"description": "<strong>Bold text</strong> and <em>italic</em>"
}
<!-- Unescaped (renders actual HTML) -->
<p>{{{description}}}</p>

Result in the generated PDF:

Bold text and italic

Use only with trusted data

Triple braces bypass Handlebars escaping. User-generated HTML can change document structure, load resources, or run scripts if your template allows it. Only use {{{variable}}} with content you control or that has been sanitized server-side.

Can I use images in my templates?

Yes. You can include images using:

  • External URLs – reference any publicly accessible image: <img src="https://example.com/logo.png" />
  • Base64 encoding – embed images directly in your template: <img src="data:image/png;base64,..." />
  • Dynamic images – pass image URLs in your template data using Handlebars: <img src="{{company_logo}}" />
  • Inline SVG – embed SVG graphics directly in your HTML for sharp, scalable icons and logos.
How do I control page breaks in templates?

Use CSS print properties to control page breaks:

/* Force a page break before an element */
.new-page { page-break-before: always; }

/* Prevent an element from being split across pages */
.keep-together { break-inside: avoid; }

/* Force a page break after an element */
.section-end { page-break-after: always; }

This is especially useful for long tables or multi-section documents. See our optimizing HTML for PDF guide for more examples.

Can I add headers and footers to template PDFs?

Yes. Enable Display Header & Footer in the template's PDF Options tab. Header and footer HTML uses Chromium placeholder classes such as pageNumber, totalPages, date, title, and url – these are separate from Handlebars variables in the main template body. See headerTemplate and footerTemplate for the full list.

How do I style my templates?

Write your styles directly in the template using one of these approaches:

<style> block in your template's <head>:

<style>
.invoice-title { font-size: 24px; font-weight: bold; color: #333; }
.total { text-align: right; border-top: 2px solid #000; }
</style>

Inline styles directly on HTML elements:

<h1 style="font-size: 24px; font-weight: bold; color: #333;">Invoice</h1>

You can combine both approaches in the same template.

Can I use JavaScript in my templates?

Yes. Templates are rendered by a full browser engine, so JavaScript works. Use waitUntil: "networkidle" to wait for all external scripts to load, or waitForFunction for more precise control over when the PDF is generated.

For example, generating a QR code with an external library:

{{#if qr_code_data}}
<canvas id="qr-code"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrious/4.0.2/qrious.min.js"></script>
<script>
new QRious({
element: document.getElementById('qr-code'),
value: {{{json qr_code_data}}}
});
</script>
{{/if}}
Safe data interpolation in JavaScript

Use the built-in {{{json value}}} helper when embedding templateData into JavaScript. It produces a properly escaped JSON literal, so quotes, backslashes, and newlines in your data cannot break the script or be injected as code.

How can I test my template before going live?

The template designer offers two preview modes:

  • Quick HTML Preview – instant visual feedback as you edit.
  • Real PDF Preview – generates an actual PDF using the API engine, so you can verify the final output including page breaks, fonts, and print styles.

Your template stays in draft until you publish it, so you can test freely without affecting production.

Do you support other templating engines besides Handlebars?

We currently support Handlebars. If you need a different engine, contact us at contact@pdfbolt.com and we'll consider adding it based on user needs.

Additional Resources

Explore these resources for detailed implementation: