Template to PDF
Generate a PDF from a template with dynamic data. Use predefined templates from the cloudlayer.io template gallery or provide your own custom HTML template. Templates support Handlebars syntax for data binding.
Endpoint
POST /v1/template/pdf
All requests are processed synchronously. The response body is the raw PDF file.
POST /v2/template/pdf
Requests default to asynchronous processing (async: true, storage: true). Add "async": false to receive the raw PDF directly. See Sync vs. Async for details.
Supports two content types:
| Content Type | Use Case |
|---|---|
application/json | Inline templates (base64-encoded) or predefined template IDs with JSON data. |
multipart/form-data | Upload template files directly alongside JSON data. |
Quick Start: Predefined Template
Use a template from the cloudlayer.io gallery by referencing its templateId:
cURL
curl -X POST "https://api.cloudlayer.io/v1/template/pdf" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"templateId": "professional-invoice",
"data": {
"invoiceNumber": "INV-2024-001",
"companyName": "Acme Corp",
"customerName": "Jane Smith",
"items": [
{"name": "Widget A", "quantity": 10, "price": 25.00},
{"name": "Widget B", "quantity": 5, "price": 40.00}
],
"total": 450.00
}
}' \
--output invoice.pdf
JavaScript (fetch)
const response = await fetch("https://api.cloudlayer.io/v1/template/pdf", {
method: "POST",
headers: {
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
templateId: "professional-invoice",
data: {
invoiceNumber: "INV-2024-001",
companyName: "Acme Corp",
customerName: "Jane Smith",
items: [
{ name: "Widget A", quantity: 10, price: 25.0 },
{ name: "Widget B", quantity: 5, price: 40.0 },
],
total: 450.0,
},
}),
});
const pdf = await response.arrayBuffer();
Python (requests)
import requests
response = requests.post(
"https://api.cloudlayer.io/v1/template/pdf",
headers={
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
json={
"templateId": "professional-invoice",
"data": {
"invoiceNumber": "INV-2024-001",
"companyName": "Acme Corp",
"customerName": "Jane Smith",
"items": [
{"name": "Widget A", "quantity": 10, "price": 25.00},
{"name": "Widget B", "quantity": 5, "price": 40.00},
],
"total": 450.00,
},
},
)
with open("invoice.pdf", "wb") as f:
f.write(response.content)
cURL
curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"templateId": "professional-invoice",
"data": {
"invoiceNumber": "INV-2024-001",
"companyName": "Acme Corp",
"customerName": "Jane Smith",
"items": [
{"name": "Widget A", "quantity": 10, "price": 25.00},
{"name": "Widget B", "quantity": 5, "price": 40.00}
],
"total": 450.00
},
"async": false
}' \
--output invoice.pdf
JavaScript (fetch)
const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
method: "POST",
headers: {
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
templateId: "professional-invoice",
data: {
invoiceNumber: "INV-2024-001",
companyName: "Acme Corp",
customerName: "Jane Smith",
items: [
{ name: "Widget A", quantity: 10, price: 25.0 },
{ name: "Widget B", quantity: 5, price: 40.0 },
],
total: 450.0,
},
async: false,
}),
});
const pdf = await response.arrayBuffer();
Python (requests)
import requests
response = requests.post(
"https://api.cloudlayer.io/v2/template/pdf",
headers={
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
json={
"templateId": "professional-invoice",
"data": {
"invoiceNumber": "INV-2024-001",
"companyName": "Acme Corp",
"customerName": "Jane Smith",
"items": [
{"name": "Widget A", "quantity": 10, "price": 25.00},
{"name": "Widget B", "quantity": 5, "price": 40.00},
],
"total": 450.00,
},
"async": False,
},
)
with open("invoice.pdf", "wb") as f:
f.write(response.content)
Tip: Omit
"async": falseto use the default async mode. The API will return a JSON response with the job ID immediately, and the generated PDF will be stored and accessible via the Assets endpoint.
Quick Start: Custom Template
Provide your own HTML template as a base64-encoded string:
cURL
TEMPLATE=$(cat <<'HTML'
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #2563eb; }
.detail { margin: 8px 0; }
.label { font-weight: bold; color: #666; }
</style>
</head>
<body>
<h1>{{title}}</h1>
<div class="detail"><span class="label">Date:</span> {{date}}</div>
<div class="detail"><span class="label">Prepared for:</span> {{recipient}}</div>
<div style="margin-top: 20px;">{{{content}}}</div>
</body>
</html>
HTML
)
TEMPLATE_BASE64=$(echo "$TEMPLATE" | base64 -w 0)
curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json" \
-d "{
\"template\": \"$TEMPLATE_BASE64\",
\"data\": {
\"title\": \"Monthly Report\",
\"date\": \"January 2024\",
\"recipient\": \"Jane Smith\",
\"content\": \"<p>This is the report content with <strong>HTML</strong> support.</p>\"
}
}" \
--output report.pdf
JavaScript (fetch)
const template = `<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #2563eb; }
.detail { margin: 8px 0; }
.label { font-weight: bold; color: #666; }
</style>
</head>
<body>
<h1>{{title}}</h1>
<div class="detail"><span class="label">Date:</span> {{date}}</div>
<div class="detail"><span class="label">Prepared for:</span> {{recipient}}</div>
<div style="margin-top: 20px;">{{{content}}}</div>
</body>
</html>`;
const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
method: "POST",
headers: {
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
template: btoa(template),
data: {
title: "Monthly Report",
date: "January 2024",
recipient: "Jane Smith",
content:
"<p>This is the report content with <strong>HTML</strong> support.</p>",
},
}),
});
const pdf = await response.arrayBuffer();
Python (requests)
import base64
import requests
template = """<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #2563eb; }
</style>
</head>
<body>
<h1>{{title}}</h1>
<p>Prepared for: {{recipient}}</p>
<div>{{{content}}}</div>
</body>
</html>"""
response = requests.post(
"https://api.cloudlayer.io/v2/template/pdf",
headers={
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
json={
"template": base64.b64encode(template.encode()).decode(),
"data": {
"title": "Monthly Report",
"recipient": "Jane Smith",
"content": "<p>Report content here.</p>",
},
},
)
with open("report.pdf", "wb") as f:
f.write(response.content)
Parameters
Template Parameters
You must provide either templateId (for a predefined template) or template (for a custom template), but not both.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
templateId | string | Conditional | — | The ID of a predefined template from the cloudlayer.io template gallery. Browse available templates in the dashboard. |
template | string | Conditional | — | A custom HTML template as a base64-encoded string. Supports Handlebars syntax for data binding. |
data | object | No | {} | Key-value pairs of data to inject into the template. Keys correspond to Handlebars placeholders in the template (e.g., {{name}} is populated by data.name). |
Base Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
autoScroll | boolean | false | Scroll the page to trigger lazy-loaded content before capture. |
delay | number | 0 | Wait time in milliseconds after the page loads before generating. |
filename | string | — | Set the Content-Disposition filename on the response. |
generatePreview | object | — | Generate a thumbnail preview image. See HTML to PDF — generatePreview. |
height | string | — | Override page height with a CSS value. |
width | string | — | Override page width with a CSS value. |
inline | boolean | false | Display the PDF inline in the browser instead of downloading. |
landscape | boolean | false | Generate in landscape orientation. |
preferCSSPageSize | boolean | false | Use CSS @page size instead of format. |
projectId | string | — | Associate with a project for dashboard organization. |
scale | number | 1 | Page rendering scale (0.1 to 2). |
timeout | number | 30000 | Maximum page load time in milliseconds. |
timeZone | string | — | Browser timezone (IANA name). |
viewPort | object | — | Browser viewport configuration. See HTML to PDF — viewPort. |
waitForSelector | object | — | Wait for a CSS selector before generating. See HTML to PDF — waitForSelector. |
waitUntil | string | "networkidle2" | Navigation completion strategy. |
PDF Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
pageRanges | string | — | Page ranges to include (e.g., "1-5", "1,3,5"). |
format | string | "letter" | Page size: "letter", "legal", "tabloid", "ledger", "a0" through "a6". |
margin | object | — | Page margins. See HTML to PDF — margin. |
printBackground | boolean | true | Include background colors and images. |
headerTemplate | object | — | Custom page header. See HTML to PDF — headerTemplate. |
footerTemplate | object | — | Custom page footer. See HTML to PDF — footerTemplate. |
Multipart Uploads
For uploading template files directly (instead of base64-encoding), use multipart/form-data:
cURL
curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \
-H "X-API-Key: your-api-key-here" \
-F "template=@./my-template.html" \
-F 'data={"invoiceNumber":"INV-001","total":450.00}'
JavaScript (fetch)
const formData = new FormData();
formData.append("template", new Blob([templateHtml], { type: "text/html" }), "template.html");
formData.append("data", JSON.stringify({
invoiceNumber: "INV-001",
total: 450.00,
}));
const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
method: "POST",
headers: {
"X-API-Key": "your-api-key-here",
// Do not set Content-Type — fetch sets it automatically with the boundary
},
body: formData,
});
const pdf = await response.arrayBuffer();
Python (requests)
import json
import requests
with open("my-template.html", "rb") as f:
template_file = f.read()
response = requests.post(
"https://api.cloudlayer.io/v2/template/pdf",
headers={"X-API-Key": "your-api-key-here"},
files={"template": ("template.html", template_file, "text/html")},
data={"data": json.dumps({"invoiceNumber": "INV-001", "total": 450.00})},
)
with open("invoice.pdf", "wb") as f:
f.write(response.content)
Template Syntax
Templates use Handlebars syntax for data binding. Here are the most common patterns:
Variable Output
<!-- Escaped output (HTML entities are escaped) -->
<p>Hello, {{name}}!</p>
<!-- Unescaped output (raw HTML is rendered) -->
<div>{{{htmlContent}}}</div>
Conditionals
{{#if isPaid}}
<span class="badge paid">Paid</span>
{{else}}
<span class="badge unpaid">Unpaid</span>
{{/if}}
Loops
<table>
<thead>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{this.name}}</td>
<td>{{this.quantity}}</td>
<td>${{this.price}}</td>
</tr>
{{/each}}
</tbody>
</table>
Nested Data
<div>
<p>{{company.name}}</p>
<p>{{company.address.street}}</p>
<p>{{company.address.city}}, {{company.address.state}} {{company.address.zip}}</p>
</div>
Full Example: Invoice with Custom Template
JavaScript (fetch)
const template = `<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #333; padding: 60px; }
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; }
.company-name { font-size: 28px; font-weight: 700; color: #1a1a1a; }
.invoice-meta { text-align: right; }
.invoice-number { font-size: 20px; font-weight: 600; color: #2563eb; }
.customer { margin-bottom: 30px; padding: 20px; background: #f8f9fa; border-radius: 8px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th { background: #2563eb; color: white; padding: 12px 16px; text-align: left; }
td { padding: 12px 16px; border-bottom: 1px solid #eee; }
tr:nth-child(even) { background: #f8f9fa; }
.total-row { font-size: 18px; font-weight: 700; background: #f0f4ff !important; }
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 12px; color: #999; }
</style>
</head>
<body>
<div class="header">
<div class="company-name">{{companyName}}</div>
<div class="invoice-meta">
<div class="invoice-number">{{invoiceNumber}}</div>
<div>Date: {{date}}</div>
<div>Due: {{dueDate}}</div>
</div>
</div>
<div class="customer">
<strong>Bill To:</strong><br>
{{customerName}}<br>
{{customerEmail}}
</div>
<table>
<thead>
<tr><th>Item</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{this.name}}</td>
<td>{{this.quantity}}</td>
<td>\${{this.price}}</td>
<td>\${{this.total}}</td>
</tr>
{{/each}}
<tr class="total-row">
<td colspan="3" style="text-align: right;">Total</td>
<td>\${{grandTotal}}</td>
</tr>
</tbody>
</table>
{{#if notes}}
<div style="margin-top: 20px; padding: 15px; background: #fffbeb; border-radius: 8px;">
<strong>Notes:</strong> {{notes}}
</div>
{{/if}}
<div class="footer">
Thank you for your business. Payment is due within 30 days.
</div>
</body>
</html>`;
const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
method: "POST",
headers: {
"X-API-Key": "your-api-key-here",
"Content-Type": "application/json",
},
body: JSON.stringify({
template: btoa(template),
data: {
companyName: "Acme Corp",
invoiceNumber: "INV-2024-042",
date: "2024-01-15",
dueDate: "2024-02-14",
customerName: "Jane Smith",
customerEmail: "jane@example.com",
items: [
{ name: "Web Development", quantity: 40, price: "150.00", total: "6,000.00" },
{ name: "Design Services", quantity: 20, price: "125.00", total: "2,500.00" },
{ name: "Hosting (Annual)", quantity: 1, price: "299.00", total: "299.00" },
],
grandTotal: "8,799.00",
notes: "Please include the invoice number in your payment reference.",
},
format: "letter",
margin: { top: "0.5in", bottom: "0.5in", left: "0.5in", right: "0.5in" },
printBackground: true,
filename: "invoice-2024-042.pdf",
}),
});
const pdf = await response.arrayBuffer();
Tips
- Handlebars triple-braces: Use
{{{variable}}}(triple curly braces) when your data contains HTML that should be rendered, not escaped. Use{{variable}}(double) for plain text to prevent XSS. - External resources: Custom templates can reference external CSS, fonts, and images via URLs. Make sure they are publicly accessible.
- Template reuse: If you use the same template repeatedly, consider adding it to the template gallery via the dashboard and referencing it by
templateId. This reduces payload size and simplifies your API calls. - Debugging templates: Test your Handlebars template locally using the Handlebars playground before sending it to the API.
- Multipart vs. JSON: Use multipart uploads when your template file is large or when you want to avoid base64 encoding overhead.