How to Generate Invoices with DocAPI
Invoices are one of the most common PDF use cases. Every business needs them, and doing them manually doesn't scale.
This guide shows you how to generate professional PDF invoices with DocAPI using HTML templates.
The Basic Approach
- Create an HTML template for your invoice
- Fill in the data (customer info, line items, totals)
- Send to DocAPI
- Get back a PDF
That's it. No wkhtmltopdf, no Puppeteer servers, no dependencies.
Simple Invoice Example
Let's start with a basic invoice:
const response = await fetch("https://api.docapi.co/v1/pdf", {
method: "POST",
headers: {
"x-api-key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
html: `
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
.header { display: flex; justify-content: space-between; margin-bottom: 40px; }
.invoice-title { font-size: 32px; color: #333; }
.invoice-number { color: #666; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
th { background: #f8f9fa; }
.total { font-size: 18px; font-weight: bold; }
</style>
</head>
<body>
<div class="header">
<div>
<div class="invoice-title">INVOICE</div>
<div class="invoice-number">#INV-001</div>
</div>
<div>
<strong>Your Company</strong><br>
123 Main St<br>
New York, NY 10001
</div>
</div>
<div style="margin-bottom: 30px;">
<strong>Bill To:</strong><br>
Acme Corp<br>
456 Oak Ave<br>
Los Angeles, CA 90001
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr>
<td>Web Development</td>
<td>40</td>
<td>$100</td>
<td>$4,000</td>
</tr>
<tr>
<td>Design Services</td>
<td>20</td>
<td>$80</td>
<td>$1,600</td>
</tr>
</tbody>
</table>
<div style="text-align: right;">
<p>Subtotal: $5,600</p>
<p>Tax (10%): $560</p>
<p class="total">Total: $6,160</p>
</div>
</body>
</html>
`,
options: {
format: "A4",
margin: { top: "20mm", bottom: "20mm", left: "20mm", right: "20mm" },
},
}),
});
const pdfBuffer = await response.arrayBuffer();Building a Reusable Template
Hardcoding HTML isn't practical. Here's how to build a reusable invoice generator:
function generateInvoiceHTML(invoice) {
const lineItems = invoice.items
.map(
(item) => `
<tr>
<td>${item.description}</td>
<td>${item.quantity}</td>
<td>$${item.price.toFixed(2)}</td>
<td>$${(item.quantity * item.price).toFixed(2)}</td>
</tr>
`
)
.join("");
const subtotal = invoice.items.reduce(
(sum, item) => sum + item.quantity * item.price,
0
);
const tax = subtotal * (invoice.taxRate || 0);
const total = subtotal + tax;
return `
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
color: #333;
line-height: 1.6;
}
.invoice { padding: 40px; }
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 50px;
padding-bottom: 20px;
border-bottom: 2px solid #eee;
}
.logo { font-size: 24px; font-weight: bold; color: #2563eb; }
.invoice-info { text-align: right; }
.invoice-number { font-size: 28px; font-weight: bold; }
.invoice-date { color: #666; margin-top: 5px; }
.addresses {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.address-block { width: 45%; }
.address-label {
font-size: 12px;
text-transform: uppercase;
color: #666;
margin-bottom: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}
th {
background: #f8fafc;
padding: 15px;
text-align: left;
font-size: 12px;
text-transform: uppercase;
color: #666;
border-bottom: 2px solid #e2e8f0;
}
td {
padding: 15px;
border-bottom: 1px solid #e2e8f0;
}
.totals {
width: 300px;
margin-left: auto;
}
.totals-row {
display: flex;
justify-content: space-between;
padding: 10px 0;
}
.totals-row.total {
font-size: 20px;
font-weight: bold;
border-top: 2px solid #333;
margin-top: 10px;
padding-top: 15px;
}
.footer {
margin-top: 60px;
padding-top: 20px;
border-top: 1px solid #eee;
text-align: center;
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<div class="invoice">
<div class="header">
<div class="logo">${invoice.company.name}</div>
<div class="invoice-info">
<div class="invoice-number">#${invoice.number}</div>
<div class="invoice-date">Date: ${invoice.date}</div>
<div class="invoice-date">Due: ${invoice.dueDate}</div>
</div>
</div>
<div class="addresses">
<div class="address-block">
<div class="address-label">From</div>
<strong>${invoice.company.name}</strong><br>
${invoice.company.address}<br>
${invoice.company.city}, ${invoice.company.state} ${invoice.company.zip}
</div>
<div class="address-block">
<div class="address-label">Bill To</div>
<strong>${invoice.customer.name}</strong><br>
${invoice.customer.address}<br>
${invoice.customer.city}, ${invoice.customer.state} ${invoice.customer.zip}
</div>
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th>Rate</th>
<th style="text-align: right;">Amount</th>
</tr>
</thead>
<tbody>
${lineItems}
</tbody>
</table>
<div class="totals">
<div class="totals-row">
<span>Subtotal</span>
<span>$${subtotal.toFixed(2)}</span>
</div>
${
invoice.taxRate
? `
<div class="totals-row">
<span>Tax (${(invoice.taxRate * 100).toFixed(0)}%)</span>
<span>$${tax.toFixed(2)}</span>
</div>
`
: ""
}
<div class="totals-row total">
<span>Total</span>
<span>$${total.toFixed(2)}</span>
</div>
</div>
${invoice.notes ? `<div class="footer">${invoice.notes}</div>` : ""}
</div>
</body>
</html>
`;
}Using the Template
const invoice = {
number: "INV-2024-001",
date: "Dec 22, 2024",
dueDate: "Jan 21, 2025",
taxRate: 0.1,
company: {
name: "Your Company",
address: "123 Main Street",
city: "New York",
state: "NY",
zip: "10001",
},
customer: {
name: "Acme Corporation",
address: "456 Oak Avenue",
city: "Los Angeles",
state: "CA",
zip: "90001",
},
items: [
{ description: "Website Development", quantity: 1, price: 5000 },
{ description: "Monthly Hosting", quantity: 12, price: 50 },
{ description: "SSL Certificate", quantity: 1, price: 100 },
],
notes: "Payment due within 30 days. Thank you for your business!",
};
const html = generateInvoiceHTML(invoice);
const response = await fetch("https://api.docapi.co/v1/pdf", {
method: "POST",
headers: {
"x-api-key": process.env.DOCAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
options: { format: "A4" },
}),
});
const pdf = await response.arrayBuffer();Express.js Endpoint
Here's a complete API endpoint for generating invoices:
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/invoices/:id/pdf", async (req, res) => {
try {
// Fetch invoice from your database
const invoice = await getInvoiceById(req.params.id);
if (!invoice) {
return res.status(404).json({ error: "Invoice not found" });
}
// Generate HTML
const html = generateInvoiceHTML(invoice);
// Generate PDF
const response = await fetch("https://api.docapi.co/v1/pdf", {
method: "POST",
headers: {
"x-api-key": process.env.DOCAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
options: { format: "A4" },
}),
});
if (!response.ok) {
throw new Error("PDF generation failed");
}
const pdfBuffer = await response.arrayBuffer();
// Send PDF
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="invoice-${invoice.number}.pdf"`
);
res.send(Buffer.from(pdfBuffer));
} catch (error) {
console.error("Invoice PDF error:", error);
res.status(500).json({ error: "Failed to generate invoice" });
}
});Adding a Logo
Include your logo using a base64 data URL or hosted image:
const logoHTML = `
<img
src="https://your-domain.com/logo.png"
alt="Company Logo"
style="height: 50px; margin-bottom: 20px;"
/>
`;Or embed it directly:
const logoBase64 = "data:image/png;base64,iVBORw0KGgo...";
const logoHTML = `<img src="${logoBase64}" style="height: 50px;" />`;Multi-Page Invoices
Long invoices automatically flow to multiple pages. Control page breaks with CSS:
/* Keep item rows together */
tr {
page-break-inside: avoid;
}
/* Force a page break before totals if needed */
.totals {
page-break-before: auto;
}
/* Repeat table headers on each page */
thead {
display: table-header-group;
}Adding Headers and Footers
DocAPI supports page headers and footers:
const response = await fetch("https://api.docapi.co/v1/pdf", {
method: "POST",
headers: {
"x-api-key": process.env.DOCAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: invoiceHTML,
options: {
format: "A4",
displayHeaderFooter: true,
headerTemplate: `
<div style="font-size: 10px; color: #666; width: 100%; text-align: center;">
Your Company - Invoice
</div>
`,
footerTemplate: `
<div style="font-size: 10px; color: #666; width: 100%; text-align: center;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
`,
},
}),
});Best Practices
1. Use Web-Safe Fonts
Stick to fonts that render consistently:
- Arial, Helvetica
- Georgia, Times New Roman
- Courier New
Or include Google Fonts via CDN.
2. Set Explicit Dimensions
Browsers and PDF renderers can differ. Be explicit:
.invoice {
width: 210mm; /* A4 width */
min-height: 297mm; /* A4 height */
}3. Test Print Styles
Use browser print preview to debug before sending to DocAPI.
4. Handle Currency Properly
Use Intl.NumberFormat for consistent currency formatting:
const formatCurrency = (amount) =>
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(amount);Common Customizations
Different Paper Sizes
// US Letter
options: { format: "Letter" }
// Custom size
options: { width: "8.5in", height: "11in" }Landscape Orientation
options: { format: "A4", landscape: true }Background Colors
options: { printBackground: true }Error Handling
Always handle API errors gracefully:
async function generateInvoicePDF(invoice) {
try {
const response = await fetch("https://api.docapi.co/v1/pdf", {
method: "POST",
headers: {
"x-api-key": process.env.DOCAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: generateInvoiceHTML(invoice),
options: { format: "A4" },
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || "PDF generation failed");
}
return await response.arrayBuffer();
} catch (error) {
console.error("Invoice generation failed:", error);
throw error;
}
}Next Steps
- Get your API key at docapi.co (100 free PDFs/month)
- Copy the template from this guide
- Customize the styling to match your brand
- Connect to your data (database, Stripe, etc.)
Need Help?
- Check out the API documentation
- Email us at [email protected]
- We're happy to help with custom templates