How to Generate a Dynamic PDF E-commerce Invoice with PDF.co
This tutorial explains how to generate an e-commerce invoice from an HTML template and dynamic JSON data using the PDF.co Web API.
The HTML template controls the invoice’s design, while the JSON data supplies values such as the order number, customer address, products, prices, and totals. You can reuse the same template to generate a different invoice for every order.
What You Will Need
Before starting, make sure you have:
- A PDF.co account
- An HTML invoice template saved in PDF.co
- Sample order data in JSON format
- Access to the PDF.co API Tester
The workflow consists of three parts:
- Create and save an HTML invoice template.
- send the template ID and order data to the PDF.co API.
- Open or download the generated PDF.
Step 1: Create an HTML Invoice Template
Sign in to your PDF.co account and open the HTML to PDF Templates section.
Create a new template and give it a descriptive name, such as:
E-commerce InvoiceHTML templates support Mustache and Handlebars placeholders. A placeholder such as {{order_id}} is replaced by the matching value from the template data.
The {{#each items}} block can be used to create one table row for each purchased product.
Here is a simplified invoice template:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
margin: 0;
color: #222;
font-family: Arial, sans-serif;
font-size: 12px;
}
.invoice {
max-width: 760px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
}
.header h1 {
margin: 0 0 8px;
color: #2457a7;
font-size: 30px;
}
.company,
.invoice-details {
line-height: 1.5;
}
.invoice-details {
text-align: right;
}
.addresses {
display: flex;
gap: 40px;
margin-bottom: 30px;
}
.address {
flex: 1;
white-space: pre-line;
}
.address h2 {
margin: 0 0 8px;
color: #555;
font-size: 14px;
text-transform: uppercase;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
padding: 10px;
color: #fff;
background: #2457a7;
text-align: left;
}
td {
padding: 10px;
border-bottom: 1px solid #ddd;
}
.number {
text-align: right;
}
.totals {
width: 320px;
margin: 24px 0 0 auto;
}
.totals td {
border: 0;
padding: 5px 10px;
}
.grand-total {
font-size: 15px;
font-weight: bold;
}
.notes {
margin-top: 35px;
padding: 15px;
background: #f3f5f8;
white-space: pre-line;
}
</style>
</head>
<body>
<div class="invoice">
<div class="header">
<div class="company">
<h1>INVOICE</h1>
<strong>{{company_name}}</strong><br>
<span style="white-space: pre-line">{{company_address}}</span>
</div>
<div class="invoice-details">
<strong>Order:</strong> {{order_id}}<br>
<strong>Order date:</strong> {{order_date}}<br>
<strong>Customer ID:</strong> {{customer_id}}<br>
<strong>Shipped date:</strong> {{shipped_date}}<br>
<strong>Shipped via:</strong> {{shipped_via}}
</div>
</div>
<div class="addresses">
<div class="address">
<h2>Bill To</h2>
<strong>{{bill_to_name}}</strong><br>
{{bill_to_address}}
</div>
<div class="address">
<h2>Ship To</h2>
<strong>{{ship_to_name}}</strong><br>
{{ship_to_address}}
</div>
</div>
<table>
<thead>
<tr>
<th>Product</th>
<th class="number">Quantity</th>
<th class="number">Unit Price</th>
<th class="number">Amount</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{name}}</td>
<td class="number">{{quantity}}</td>
<td class="number">{{price}}</td>
<td class="number">{{line_total}}</td>
</tr>
{{/each}}
</tbody>
</table>
<table class="totals">
<tr>
<td>Subtotal</td>
<td class="number">{{subtotal}}</td>
</tr>
<tr>
<td>Shipping</td>
<td class="number">{{freight}}</td>
</tr>
<tr>
<td>Tax</td>
<td class="number">{{tax}}</td>
</tr>
<tr class="grand-total">
<td>Total</td>
<td class="number">{{total}}</td>
</tr>
</table>
{{#if notes}}
<div class="notes">
<strong>Notes</strong><br>
{{notes}}
</div>
{{/if}}
</div>
</body>
</html>Save the template.
Step 2: Copy the Template ID
After saving the template, locate its numeric template ID. The ID may appear in the template list, template details, or template-editing URL.
Copy this value. You will use it as the request’s templateId.
The template ID must belong to the PDF.co account used to authenticate the request.
Step 3: Prepare the Invoice Data
Create a JSON object whose property names match the placeholders in the HTML template.
For example:
{
"company_name": "Northwind Market",
"company_address": "1234 Market Street\nSan Francisco, CA 94102\nUSA",
"order_id": "1122455",
"order_date": "September 2, 2026",
"customer_id": "C-8001",
"shipped_date": "September 3, 2026",
"shipped_via": "UPS",
"bill_to_name": "John Doe",
"bill_to_address": "435 South La Fayette Park Place\nLos Angeles, CA 90057\nUSA",
"ship_to_name": "Jane Doe",
"ship_to_address": "18144 El Camino Real\nSunnyvale, CA 94087\nUSA",
"items": [
{
"name": "Wireless Keyboard",
"quantity": 2,
"price": "$50.00",
"line_total": "$100.00"
},
{
"name": "USB-C Dock",
"quantity": 1,
"price": "$150.00",
"line_total": "$150.00"
},
{
"name": "Laptop Stand",
"quantity": 2,
"price": "$35.00",
"line_total": "$70.00"
}
],
"subtotal": "$320.00",
"freight": "$19.95",
"tax": "$26.40",
"total": "$366.35",
"notes": "Thank you for your purchase."
}Calculate monetary totals in the e-commerce platform or application creating the request. The PDF template should generally display finalized values rather than perform authoritative financial calculations.
Step 4: Open the PDF.co API Tester
Open the PDF.co API Tester while signed in to your PDF.co account.
Select the following endpoint:
POST /v1/pdf/convert/from/htmlThis endpoint supports both raw HTML and saved HTML templates. When using a saved template, supply templateId and templateData.
The current endpoint and its parameters are documented in the PDF from HTML Template API reference.
Step 5: Configure the Request
Enter the following request values:
- Template ID: Your saved HTML template ID
- Template Data: The invoice data serialized as a JSON string
- Name:
ecommerce-invoice-1122455.pdf - Paper Size:
Letter - Orientation:
Portrait - Margins:
20px - Print Background:
true - Media Type:
print - Async:
false
A complete JSON request body has this structure:
{
"templateId": 123,
"name": "ecommerce-invoice-1122455.pdf",
"paperSize": "Letter",
"orientation": "Portrait",
"margins": "20px",
"printBackground": true,
"mediaType": "print",
"async": false,
"templateData": "{\"company_name\":\"Northwind Market\",\"company_address\":\"1234 Market Street\\nSan Francisco, CA 94102\\nUSA\",\"order_id\":\"1122455\",\"order_date\":\"September 2, 2026\",\"customer_id\":\"C-8001\",\"shipped_date\":\"September 3, 2026\",\"shipped_via\":\"UPS\",\"bill_to_name\":\"John Doe\",\"bill_to_address\":\"435 South La Fayette Park Place\\nLos Angeles, CA 90057\\nUSA\",\"ship_to_name\":\"Jane Doe\",\"ship_to_address\":\"18144 El Camino Real\\nSunnyvale, CA 94087\\nUSA\",\"items\":[{\"name\":\"Wireless Keyboard\",\"quantity\":2,\"price\":\"$50.00\",\"line_total\":\"$100.00\"},{\"name\":\"USB-C Dock\",\"quantity\":1,\"price\":\"$150.00\",\"line_total\":\"$150.00\"},{\"name\":\"Laptop Stand\",\"quantity\":2,\"price\":\"$35.00\",\"line_total\":\"$70.00\"}],\"subtotal\":\"$320.00\",\"freight\":\"$19.95\",\"tax\":\"$26.40\",\"total\":\"$366.35\",\"notes\":\"Thank you for your purchase.\"}"
}Replace 123 with the actual template ID.
The templateData parameter is a string containing JSON. When assembling the request programmatically, serialize the invoice object with the standard JSON function provided by your programming language. In JavaScript, for example, use:
const requestBody = {
templateId: 123,
name: "ecommerce-invoice-1122455.pdf",
paperSize: "Letter",
orientation: "Portrait",
margins: "20px",
printBackground: true,
mediaType: "print",
async: false,
templateData: JSON.stringify(invoiceData)
};This avoids manually escaping every quotation mark.
Step 6: Run the Request
Run the request in the API Tester.
A successful synchronous response resembles:
{
"url": "https://pdf-temp-files.s3.amazonaws.com/.../ecommerce-invoice-1122455.pdf",
"pageCount": 1,
"error": false,
"status": 200,
"name": "ecommerce-invoice-1122455.pdf",
"remainingCredits": 1000
}Confirm that:
errorisfalse.statusis200.urlcontains the temporary location of the generated PDF.pageCountcontains the number of generated pages.
Step 7: Review the Generated Invoice
Open the returned URL and inspect the invoice.
Verify that:
- The order and customer information is correct.
- Billing and shipping addresses retain their line breaks.
- Every product appears in the table.
- Quantities, prices, and totals are correct.
- Long product lists continue onto additional pages properly.
- Colors and other background styling appear in the PDF.
- No content is cut off by the page margins.
If the layout needs adjustment, edit the HTML template, save it, and run the same request again.
Step 8: Integrate the Request into an E-commerce Workflow
After validating the template in the API Tester, call the same endpoint from your application or automation workflow.
A production workflow might:
- Receive a paid-order event from an e-commerce platform.
- Validate the order and customer data.
- Calculate the subtotal, discounts, shipping, tax, and total.
- Serialize the order data into
templateData. - Send it to PDF.co with the template ID.
- Download the generated PDF from the returned URL.
- Save the invoice in permanent storage.
- Email the invoice to the customer.
PDF.co output URLs are temporary. Download or transfer the generated invoice to permanent storage before the output link expires.
Troubleshooting
A Placeholder Appears Unchanged
Confirm that its name exactly matches a property in the JSON data. Placeholder names and JSON property names are case-sensitive.
For example:
{{order_id}}requires:
{
"order_id": "1122455"
}The Items Table Is Empty
Confirm that items is an array and that the template places its table row inside:
{{#each items}}
...
{{/each}}The API Reports Invalid Template Data
The templateData parameter must be a string. Serialize the invoice-data object before placing it in the request body.
Background Colors Are Missing
Set printBackground to true.
The Header or Footer Overlaps the Invoice
Increase the corresponding top or bottom margin. Headers and footers require enough page space to render without covering the main content.
The Output Link No Longer Works
Generated output links are temporary. Run the request again or save the PDF to permanent storage immediately after it is generated.
The Wrong Template Is Used
Check that the templateId belongs to the authenticated PDF.co account and identifies the intended HTML template.
Conclusion
You have created a reusable HTML invoice template and populated it with dynamic e-commerce order data through the PDF.co Web API. The same template can now generate invoices for different customers and orders by changing only the templateData value.
This approach separates invoice design from order data, making the workflow easier to maintain and integrate with e-commerce platforms, web applications, and automation services.
Related Tutorials

