This is a practical, end-to-end example based on the real HookPDF flow:
- You create a template in HTML + CSS.
- You place Handlebars variables in the template.
- You send JSON payload with
template_idto/render. - You receive a queued job and track status.
No abstract theory in this post. We will build one invoice example from start to finish.
Step 1 - Create the invoice template
Below is a minimal invoice body with dynamic fields.
<div class="invoice-wrap">
<header class="invoice-header">
<h1>Invoice</h1>
<p>Invoice No: {{invoice_number}}</p>
<p>Issue Date: {{issue_date}}</p>
<p>Due Date: {{due_date}}</p>
</header>
<section class="bill-to">
<h2>Bill To</h2>
<p>{{customer.name}}</p>
<p>{{customer.email}}</p>
<p>{{customer.address}}</p>
</section>
<table class="invoice-table">
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Unit Price</th>
<th>Line Total</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>{{unit_price}}</td>
<td>{{line_total}}</td>
</tr>
{{/each}}
</tbody>
</table>
<section class="totals">
<p>Subtotal: {{subtotal}}</p>
<p>Tax: {{tax}}</p>
<p class="grand-total">Total: {{total}}</p>
</section>
</div>
Step 2 - Add print-safe CSS
@page {
size: A4;
margin: 20mm;
}
body {
font-family: Arial, sans-serif;
font-size: 12px;
color: #111827;
}
.invoice-table {
width: 100%;
border-collapse: collapse;
margin-top: 16px;
}
.invoice-table th,
.invoice-table td {
border-bottom: 1px solid #d1d5db;
padding: 8px;
text-align: left;
}
.grand-total {
font-weight: 700;
font-size: 14px;
}
Keep CSS simple and explicit. The goal is consistency across documents, not visual experimentation.
Step 3 - Build JSON payload that matches your variables
Every variable in the template should be satisfiable by one payload path.
{
"invoice_number": "INV-2026-0042",
"issue_date": "2026-03-19",
"due_date": "2026-04-02",
"customer": {
"name": "Northwind Labs",
"email": "billing@northwind.example",
"address": "42 Market Street, San Francisco"
},
"items": [
{
"description": "Pro Plan - March",
"quantity": 1,
"unit_price": "$99.00",
"line_total": "$99.00"
},
{
"description": "Onboarding support",
"quantity": 2,
"unit_price": "$75.00",
"line_total": "$150.00"
}
],
"subtotal": "$249.00",
"tax": "$24.90",
"total": "$273.90"
}
Step 4 - Send render request
Now call the API with your saved template id.
curl -X POST "https://api.hookpdf.com/v1/render" \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{
"template_id": "1c24f959-8e1a-4a14-acc4-cf57b4e60e99",
"payload": {
"invoice_number": "INV-2026-0042",
"issue_date": "2026-03-19",
"due_date": "2026-04-02",
"customer": {
"name": "Northwind Labs",
"email": "billing@northwind.example",
"address": "42 Market Street, San Francisco"
},
"items": [
{
"description": "Pro Plan - March",
"quantity": 1,
"unit_price": "$99.00",
"line_total": "$99.00"
}
],
"subtotal": "$99.00",
"tax": "$9.90",
"total": "$108.90"
}
}'
Typical response:
{
"job_id": "7f2a6c3f-4e83-4da4-aac8-5c0b7d7adf5b",
"status": "queued",
"is_preview": false
}
Step 5 - Check job status
Use job_id to check render status.
curl -X GET "https://api.hookpdf.com/v1/reports/7f2a6c3f-4e83-4da4-aac8-5c0b7d7adf5b" \
-H "Authorization: Bearer <api_key>"
When status becomes completed, the response includes output_url.
Common mistakes to avoid
- Variable mismatch: template expects
{{customer.name}}but payload only hasname. - Array mismatch: template loops over
itemsbut payload sendslines. - Formatting mismatch: payload sends raw number but template expects formatted currency text.
- Huge template edits without test payload: always validate with one known-good JSON.
Use this template for free now
Start with this template in HookPDF and render your first PDF for free.
Use this template freeContinue with stronger variable patterns
If your team has frequent mapping mistakes, read this next:
- Handlebars variable patterns that keep JSON and template in sync
- Template design checklist for stable PDFs
- HookPDF homepage
Ready to move from prototype to production?
Create your account and generate your first API-driven PDF with HookPDF.
Get your API key