Templates API
Templates API
Templates allow you to create reusable PDF designs with variables that can be injected at generation time. Use block-based templates for complex layouts.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/templates |
Create a new template |
| GET | /v1/templates |
List all templates |
| GET | /v1/templates/:id |
Get template details |
| PUT | /v1/templates/:id |
Update a template |
| DELETE | /v1/templates/:id |
Delete a template |
Create Template
POST /v1/templates
Request Body
{
"name": "Invoice Template",
"description": "Standard invoice layout",
"html": "<h1>Invoice for {{.customer_name}}</h1><p>Total: ${{.total}}</p>",
"is_public": false
}
Use {{.variable}} syntax (Go-template style, with a leading dot). See Template Variables below for the full substitution rules.
Response
{
"success": true,
"data": {
"template_id": "tpl_abc123",
"name": "Invoice Template",
"created_at": "2024-01-15T10:30:00Z"
}
}
Template Variables
Templates use Go-style placeholder syntax ({{.variableName}} — note the leading dot) powered by Go's html/template package with a curated set of formatting helpers from Sprig. You get contextual auto-escaping (HTML, JS, CSS, URL), loops, conditionals, nested access, and helpers like default, printf, upper, date — safe by default against injection.
Basic substitution
<h1>Hello {{.name}}!</h1>
<p>Email: {{.email}}</p>
{
"template_id": "tpl_abc123",
"variables": {
"name": "John Doe",
"email": "john@example.com"
}
}
Loops — {{range}}
Iterate over arrays. Essential for invoices and reports.
<h1>Invoice {{.number}}</h1>
<table>
{{range .items}}
<tr><td>{{.desc}}</td><td>{{.qty}}</td><td>${{printf "%.2f" .total}}</td></tr>
{{end}}
</table>
{
"template_id": "...",
"variables": {
"number": "INV-42",
"items": [
{"desc": "Widget", "qty": 2, "total": 20.00},
{"desc": "Gadget", "qty": 1, "total": 50.00}
]
}
}
Conditionals — {{if}} / {{else}}
<h1>Certificate for {{.name}}{{if .honors}} — {{.honors}}{{end}}</h1>
<p>{{if eq .status "paid"}}✓ PAID{{else}}UNPAID{{end}}</p>
Nested property access
<p>To: {{.recipient.name}}</p>
<p>{{.recipient.address.street}}<br>{{.recipient.address.city}}, {{.recipient.address.country}}</p>
Scoped blocks — {{with}}
{{with .customer}}
<strong>{{.name}}</strong><br>{{.email}}
{{end}}
Defaults for optional fields
Use the default helper inside the template for per-field fallbacks:
<p>Tax ID: {{default "N/A" .tax_id}}</p>
Or declare a default_value on the variable itself (see json_schema below) — the server injects the default when the field is absent from the request.
Formatting helpers
Common ones (full list: Sprig's string, date, math docs):
| Helper | Example | Output |
|---|---|---|
printf |
{{printf "$%.2f" 3.5}} |
$3.50 |
upper / lower / title |
{{upper "hi"}} |
HI |
trim |
{{trim " x "}} |
x |
replace |
{{replace "-" "_" .sku}} |
dashes → underscores |
date |
{{date "Jan 2, 2006" .signed_at}} |
Apr 14, 2026 |
default / coalesce |
{{default "n/a" .x}} |
fallback |
len / first / last |
{{len .items}} |
3 |
add / sub / mul / div |
{{mul .qty .price}} |
number |
Schema enforcement
Templates can declare a schema of expected variables. Required fields missing from a request return 400 VARIABLE_VALIDATION_FAILED; optional fields with a default_value are auto-filled.
Store the schema as a JSON-encoded string on the json_schema field when you create or update a template:
{
"name": "Invoice",
"html": "<h1>{{.invoice_number}}</h1>",
"json_schema": "[{\"name\":\"invoice_number\",\"type\":\"text\",\"required\":true,\"default_value\":\"\"},{\"name\":\"notes\",\"type\":\"text\",\"required\":false,\"default_value\":\"No notes.\"}]"
}
Schema fields:
name— variable identifier used in the template ({{.name}})type— one oftext,number,date,boolean,image_url,array(advisory; used by the designer UI)required—truerejects requests that omit this field unless adefault_valueis setdefault_value— string used when the field is missing from the request
Substitution rules
- Auto-escaped by default — value contents are HTML-escaped contextually (so
<script>alert(1)</script>renders as visible text, not live script). URL, JS, and CSS contexts each get the right escape. - SSTI-safe — a variable value containing
{{.admin_secret}}renders as literal text, never re-parsed as a template. - Missing optional variables render as empty strings. Missing required variables return
400 VARIABLE_VALIDATION_FAILEDwithdetails.fieldidentifying the missing name. - Extra variables are ignored silently.
template_idtakes precedence over inlinehtml. If both are supplied, the template wins.- Deleted or non-existent
template_id→404 TEMPLATE_NOT_FOUND.
Complete example — Invoice with line items
<!DOCTYPE html>
<html>
<head><title>Invoice {{.invoice_number}}</title></head>
<body>
<h1>Invoice {{.invoice_number}}</h1>
<p>Bill to: {{.customer.name}}<br>{{.customer.email}}</p>
<p>Issued: {{date "Jan 2, 2006" .issued_at}}</p>
<table>
<thead><tr><th>Item</th><th>Qty</th><th>Price</th><th>Total</th></tr></thead>
<tbody>
{{range .items}}
<tr>
<td>{{.description}}</td>
<td>{{.quantity}}</td>
<td>${{printf "%.2f" .unit_price}}</td>
<td>${{printf "%.2f" .line_total}}</td>
</tr>
{{end}}
</tbody>
</table>
<p>Subtotal: ${{printf "%.2f" .subtotal}}</p>
{{if .tax}}<p>Tax: ${{printf "%.2f" .tax}}</p>{{end}}
<p><strong>Total: ${{printf "%.2f" .total}}</strong></p>
<p>Notes: {{default "Thank you for your business." .notes}}</p>
</body>
</html>
{
"template_id": "tpl_abc123",
"variables": {
"invoice_number": "INV-42",
"customer": {"name": "Acme Corp", "email": "ap@acme.example"},
"issued_at": "2026-04-14T09:00:00Z",
"items": [
{"description": "Consulting", "quantity": 10, "unit_price": 150, "line_total": 1500},
{"description": "Integration", "quantity": 1, "unit_price": 500, "line_total": 500}
],
"subtotal": 2000,
"tax": 320,
"total": 2320
}
}
List Templates
GET /v1/templates
Query Parameters
| Parameter | Type | Description |
|---|---|---|
page |
integer | Page number (default: 1) |
limit |
integer | Results per page (default: 20, max: 100) |
search |
string | Search by name or description |
Example
curl https://lightningpdf.dev/api/v1/templates?page=1&limit=10 \
-H "Authorization: Bearer lpdf_your_api_key"
Response
{
"success": true,
"data": {
"templates": [
{
"template_id": "tpl_abc123",
"name": "Invoice Template",
"description": "Standard invoice layout",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 1,
"total_pages": 1
}
}
}
Get Template
GET /v1/templates/:id
curl https://lightningpdf.dev/api/v1/templates/tpl_abc123 \
-H "Authorization: Bearer lpdf_your_api_key"
Response
{
"success": true,
"data": {
"template_id": "tpl_abc123",
"name": "Invoice Template",
"description": "Standard invoice layout",
"html": "<h1>Invoice for {{customer_name}}</h1>...",
"variables": {
"customer_name": "string",
"items": "array",
"total": "number"
},
"options": {
"format": "A4",
"print_background": true
},
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
Update Template
PUT /v1/templates/:id
Request Body
{
"name": "Updated Invoice Template",
"html": "<h1>INVOICE</h1><p>Customer: {{customer_name}}</p>",
"variables": {
"customer_name": "string"
}
}
Response
{
"success": true,
"data": {
"template_id": "tpl_abc123",
"updated_at": "2024-01-15T14:30:00Z"
}
}
Delete Template
DELETE /v1/templates/:id
curl -X DELETE https://lightningpdf.dev/api/v1/templates/tpl_abc123 \
-H "Authorization: Bearer lpdf_your_api_key"
Response
{
"success": true,
"message": "Template deleted successfully"
}
Complete Example: Invoice System
1. Create Template
import requests
API_KEY = "lpdf_your_api_key"
BASE_URL = "https://lightningpdf.dev/api/v1"
def create_invoice_template():
html = """
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial; margin: 2cm; }
.header { text-align: center; margin-bottom: 2cm; }
.invoice-table { width: 100%; border-collapse: collapse; }
.invoice-table th, .invoice-table td {
border: 1px solid #ddd;
padding: 8px;
}
.total { text-align: right; font-size: 18px; margin-top: 1cm; }
</style>
</head>
<body>
<div class="header">
<h1>INVOICE #{{invoice_number}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer_name}}</p>
</div>
<table class="invoice-table">
<thead>
<tr>
<th>Item</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{description}}</td>
<td>{{quantity}}</td>
<td>${{price}}</td>
<td>${{total}}</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="total">
<p>Subtotal: ${{subtotal}}</p>
<p>Tax ({{tax_rate}}%): ${{tax_amount}}</p>
<p><strong>Total: ${{total}}</strong></p>
</div>
{{#if notes}}
<div style="margin-top: 2cm;">
<p><strong>Notes:</strong></p>
<p>{{notes}}</p>
</div>
{{/if}}
</body>
</html>
"""
response = requests.post(
f"{BASE_URL}/templates",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"name": "Invoice Template v1",
"description": "Standard invoice with line items and tax",
"html": html,
"variables": {
"invoice_number": "string",
"date": "string",
"customer_name": "string",
"items": "array",
"subtotal": "number",
"tax_rate": "number",
"tax_amount": "number",
"total": "number",
"notes": "string"
},
"options": {
"format": "Letter",
"print_background": True
}
}
)
return response.json()["data"]["template_id"]
2. Generate Invoice
def generate_invoice(template_id, invoice_data):
response = requests.post(
f"{BASE_URL}/pdf/generate",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
json={
"template_id": template_id,
"variables": invoice_data
}
)
result = response.json()
if result["success"]:
import base64
pdf_bytes = base64.b64decode(result["data"]["pdf"])
return pdf_bytes
else:
raise Exception(result["error"]["message"])
# Usage
template_id = create_invoice_template()
invoice_data = {
"invoice_number": "INV-2024-001",
"date": "January 15, 2024",
"customer_name": "Acme Corp",
"items": [
{"description": "Widget", "quantity": 10, "price": 25.00, "total": 250.00},
{"description": "Gadget", "quantity": 5, "price": 50.00, "total": 250.00}
],
"subtotal": 500.00,
"tax_rate": 8.5,
"tax_amount": 42.50,
"total": 542.50,
"notes": "Payment due within 30 days"
}
pdf = generate_invoice(template_id, invoice_data)
with open("invoice.pdf", "wb") as f:
f.write(pdf)
Error Codes
| Code | Description |
|---|---|
template_not_found |
Template ID does not exist |
invalid_template |
Template HTML or structure is invalid |
missing_variables |
Required variables not provided |
invalid_variables |
Variable types don't match schema |
Best Practices
- Version your templates: Use descriptive names like "Invoice v2" when updating
- Define variable schemas: Always specify variable types for validation
- Test with sample data: Use the preview feature before production
- Cache template IDs: Store template IDs in your database for faster lookups
- Use blocks for complex layouts: Block-based templates are easier to maintain