Skip to main content

How to Convert HTML to PDF Using an API: Quick Start Guide

· 10 min read
Milena Szymanowska
Milena Szymanowska
PDFBolt Co-Founder

HTML to PDF API Conversion Process showing API-based PDF generation

If you need to generate PDFs from HTML in a production app, you have two options: run a local rendering tool yourself (Puppeteer, Playwright, wkhtmltopdf) or call an HTML to PDF API that does the rendering for you. This tutorial covers the API approach with a complete Node.js example that generates invoice PDFs using EJS templates and the PDFBolt API. You will also find a comparison with headless browser solutions to help you pick the right tool for your project.

Why Choose API-Based HTML to PDF Conversion?

An HTML to PDF API is a web service that accepts HTML (or a URL) and returns a rendered PDF. Instead of installing and maintaining a headless browser on your server, you send an HTTP request and get the file back.

How HTML to PDF APIs Work

  1. Send HTML content – Your application sends either HTML content or a URL to the API.
  2. Server-side processing – The API renders the document with CSS, JavaScript, and styling.
  3. PDF conversion – The rendered HTML is converted to a professional PDF document.
  4. Receive PDF – The file is returned as a downloadable link or binary data.

Benefits

  • No need to install, configure, or maintain headless browsers on your servers.
  • The API provider handles rendering engine updates and infrastructure scaling.
  • Integration takes a few lines of code and an API key.
  • Services like PDFBolt offer GDPR compliance, secure data handling, and direct S3 uploads.
  • No browser compatibility issues or software updates to manage on your side.

Drawbacks

  • Requires internet connectivity for every conversion.
  • API services use tiered pricing (though many, including PDFBolt, offer a free plan).
  • Less low-level control than running your own headless browser instance.

HTML to PDF API vs. Headless Browser Solutions: A Detailed Comparison

HTML to PDF API vs. Headless Browser Solutions

Most HTML to PDF APIs, including PDFBolt, run headless browsers (like Playwright) under the hood. The difference is that you do not manage the browser yourself. The table below compares self-hosted headless browsers with an API-based approach, using PDFBolt as an example. For a broader look at Node.js libraries, see our Node.js PDF generation libraries comparison.

FeatureHeadless Browser (Playwright/Puppeteer)HTML to PDF API (e.g., PDFBolt)
Setup ComplexityRequires installing and maintaining headless browser environment.Simple API integration with an API key and HTTP request.
Infrastructure RequirementsConsumes server resources and requires optimization.No infrastructure management - cloud-based processing.
Performance Under LoadHigh CPU/RAM usage, degrades with concurrent conversions.Optimized for high-volume processing with consistent performance.
Browser Engine UpdatesManual updates for compatibility and security.Uses latest rendering technology without developer intervention.
Scaling for ProductionComplex infrastructure planning for high-volume needs.Automatic scaling without additional configuration.
Customization OptionsProgrammatic page manipulation offers high flexibility.API parameters for headers, footers, margins, and page sizes.
Security and ComplianceRequires implementing your own security protocols.GDPR compliance, automated data deletion, and direct S3 upload options.
Implementation TimeLonger for proper setup and configuration.Quick and easy integration.
Error Handling CapabilitiesCustom error handling required, complex debugging.Standardized error response formats, simpler troubleshooting.
Total Cost ConsiderationFree software with infrastructure and maintenance costs.Free plan available with flexible paid plans for higher volumes.

Step-by-Step Guide: Generate Invoice PDF with Node.js and HTML to PDF API

This section walks through a complete Node.js project that generates invoice PDFs using EJS for templating and the PDFBolt HTML to PDF API for rendering.

tip

Get the complete project code with templates, sample data, and ready-to-use scripts from our GitHub repository. Clone it and get started right away!

1. Create Your HTML to PDF API Account and Get API Key

  • Visit PDFBolt.com to create your free account.
  • Navigate to the Dashboard → API Keys section to find your unique API key.
  • This key authenticates all your API requests and provides access to the conversion service.

2. Prepare Your Node.js Environment

Ensure you have Node.js installed on your system. You can download it from nodejs.org if needed.

3. Create and Initialize Your Project

mkdir invoice-pdf-generator
cd invoice-pdf-generator
npm init -y

4. Install Required Dependencies

npm install axios ejs

5. Set Up Your Project Structure

For better organization, create this folder structure:

invoice-pdf-generator/
├── data/ // JSON data storage
│ └── invoice-data.json // Sample invoice data
├── templates/ // HTML/EJS templates
│ └── invoice.ejs // Invoice template
├── generate-pdf.js // Main PDF generation script
└── package.json // Project configuration

6. Create Your Invoice Template

  • EJS is one of many HTML template engines you can use. It is an HTML file with placeholders that will be dynamically filled with data from a separate file.
  • Save this template as invoice.ejs in the templates directory.
Click to view example EJS template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice</title>
<style>
:root {
--brand-color: #0C8AE5;
--bg-light-brand-color: #e4f3ff;
--bg-light: #f8f9fa;
--text-dark: #333333;
--text-medium: #555555;
--border: #e0e0e0;
}

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
font-family: Arial, sans-serif;
line-height: 1.5;
color: var(--text-dark);
padding: 20px;
}

.invoice {
max-width: 800px;
margin: 0 auto;
padding: 40px;
}

.header {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
border-bottom: 2px solid var(--brand-color);
padding-bottom: 20px;
}

.logo {
max-width: 200px;
height: auto;
}

.invoice-title {
text-align: right;
}

.invoice-title h1 {
color: var(--brand-color);
font-size: 32px;
margin-bottom: 5px;
}

.invoice-number {
font-size: 14px;
color: var(--text-medium);
}

.dates {
display: flex;
justify-content: space-between;
background-color: var(--bg-light);
padding: 15px 25px;
border-radius: 5px;
margin-bottom: 30px;
}

.date-group h3 {
font-size: 14px;
text-transform: uppercase;
margin-bottom: 5px;
color: var(--text-medium);
}

.addresses {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
gap: 20px;
}

.address {
flex: 1;
padding: 15px 25px;
border: 1px solid var(--border);
border-radius: 5px;
}

.address h3 {
font-size: 18px;
margin-bottom: 10px;
color: var(--brand-color);
border-bottom: 1px solid var(--border);
padding-bottom: 5px;
}

.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
margin-top: 15px;
border: 1px solid var(--border);
}

.items-table th {
background-color: var(--brand-color);
color: white;
text-align: left;
padding: 10px;
}

.items-table td {
padding: 10px;
border-bottom: 1px solid var(--border);
}

.items-table tr:nth-child(even) {
background-color: var(--bg-light);
}

.total-row {
font-weight: bold;
background-color: var(--bg-light-brand-color);
}

.total-label {
text-align: right;
}

.total-amount {
font-size: 18px;
text-align: right;
}

.footer {
margin-top: 30px;
border-top: 1px solid var(--border);
padding-top: 20px;
text-align: center;
}

.thank-you {
font-size: 16px;
font-weight: bold;
color: var(--brand-color);
margin-bottom: 10px;
}

.contact {
color: var(--text-medium);
font-size: 14px;
}

.contact a {
color: var(--brand-color);
text-decoration: none;
}

@media (max-width: 768px) {
.header, .addresses {
flex-direction: column;
}

.invoice-title {
text-align: left;
margin-top: 15px;
}

.date-group {
margin-bottom: 10px;
}
}
</style>
</head>
<body>
<div class="invoice">
<div class="header">
<img class="logo" src="<%= logoUrl %>" alt="Company Logo">
<div class="invoice-title">
<h1>Invoice</h1>
<div class="invoice-number"><%= invoiceNumber %></div>
</div>
</div>

<div class="dates">
<div class="date-group">
<h3>Issue Date</h3>
<div><%= issueDate %></div>
</div>
<div class="date-group">
<h3>Due Date</h3>
<div><%= dueDate %></div>
</div>
</div>

<div class="addresses">
<div class="address">
<h3>Billed To</h3>
<div><strong><%= customerName %></strong></div>
<div><%= customerAddress %></div>
</div>
<div class="address">
<h3>From</h3>
<div><strong><%= sellerName %></strong></div>
<div><%= sellerAddress %></div>
</div>
</div>

<h3>Order Summary</h3>
<table class="items-table">
<thead>
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<% items.forEach(item => { %>
<tr>
<td class="item-description"><%= item.description %></td>
<td class="item-quantity"><%= item.quantity %></td>
<td class="item-price"><%= item.price %></td>
<td class="item-total"><%= item.total %></td>
</tr>
<% }); %>
</tbody>
<tfoot>
<tr class="total-row">
<td colspan="3" class="total-label">Total Amount:</td>
<td class="total-amount"><%= total %></td>
</tr>
</tfoot>
</table>

<div class="footer">
<p class="thank-you">Thank you for your business!</p>
<p class="contact">If you have any questions, please contact us at <a href="mailto:<%= contactEmail %>"><%= contactEmail %></a></p>
</div>
</div>
</body>
</html>

7. Create Sample Invoice Data

  • Now that you have your template ready, you need data to populate it.
  • Create a file named invoice-data.json in your data directory.
Here's an example of the data
{
"invoiceNumber": "INV-2025-0327",
"issueDate": "2025-03-27",
"dueDate": "2025-04-10",
"customerName": "John Null",
"customerAddress": "127.0.0.1, Localhost, Web City",
"sellerName": "PDFBolt",
"sellerAddress": "123 Software Street, Tech Valley",
"contactEmail": "contact@pdfbolt.com",
"logoUrl": "https://img.pdfbolt.com/logo.png",
"items": [
{
"description": "Premium API Subscription",
"quantity": 12,
"price": "$20",
"total": "$240"
},
{
"description": "Developer Integration Package",
"quantity": 3,
"price": "$50",
"total": "$150"
},
{
"description": "Custom Template Design",
"quantity": 1,
"price": "$250",
"total": "$250"
},
{
"description": "Legacy System Integration",
"quantity": 5,
"price": "$500",
"total": "$2,500"
},
{
"description": "PDF Analytics Service",
"quantity": 1,
"price": "$350",
"total": "$350"
}
],
"total": "$3,490"
}

8. Write the PDF Generation Script

Now create the main script named generate-pdf.js in your project's root directory that will bring everything together. This script will:

  1. Load the invoice data from the JSON file.
  2. Render the EJS template with this data.
  3. Send the rendered HTML to the PDFBolt API.
  4. Save the resulting PDF.
Expand to see complete code
const ejs = require('ejs');
const axios = require('axios');
const fs = require('fs');
const path = require('path');

// PDFBolt API configuration
const API_URL = 'https://api.pdfbolt.com/v1/direct';
const API_KEY = 'YOUR-API-KEY'; // Replace with your actual API key

(async () => {
try {
// Load invoice data from JSON file
const invoiceData = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'invoice-data.json'), 'utf8'));

// Render EJS template with invoice data
const templatePath = path.join(__dirname, 'templates', 'invoice.ejs');
const html = await ejs.renderFile(templatePath, invoiceData);

// Encode HTML to Base64 (required by PDFBolt API)
const htmlBase64 = Buffer.from(html).toString('base64');

// Generate timestamped filename for the PDF
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');

// Send request to PDFBolt API
const response = await axios.post(
API_URL,
{
html: htmlBase64,
printBackground: true,
format: 'A4'
// Add more parameters as needed from PDFBolt API documentation
},
{
headers: {
'Content-Type': 'application/json',
'API-KEY': API_KEY
},
responseType: 'arraybuffer' // Receive binary PDF data
}
);

// Save the generated PDF locally
const pdfPath = `invoice-${timestamp}.pdf`;
fs.writeFileSync(pdfPath, response.data);
console.log(`PDF saved successfully: ${pdfPath}`);
} catch (error) {
// This block is used to debug and log errors in case the PDF generation fails
const responseData = error.response?.data;
if (responseData) {
try {
// Try to parse the error as JSON to extract detailed error information
const errorDetails = JSON.parse(responseData.toString('utf8'));
console.error('API Error Details:', errorDetails);
} catch (parseError) {
// If not JSON, log as text
console.error('API Error Response:', responseData.toString('utf8'));
}
} else {
// If no response data is available, log the error message
console.error('Error generating PDF:', error.message);
}
}
})();

Customizing Your PDF Output

You can control page size, margins, headers/footers, and more through API parameters. Here are some common options:

{
html: htmlBase64, // Base64-encoded HTML content

// Page parameters
deviceScaleFactor: 2, // Improve resolution (1-4)
emulateMediaType: 'screen', // Determines the media type: screen or print

// PDF layout parameters
format: 'A4', // Page size: 'A4', 'Letter', 'Legal', etc.
landscape: false, // Page orientation
scale: 1.1, // Content scaling factor (0.1-2)
printBackground: true, // Include background colors and images
preferCssPageSize: true, // Use CSS @page size over format parameter

// Margins
margin: {
top: '10px',
right: '10px',
bottom: '10px',
left: '10px'
},

// Headers and footers
displayHeaderFooter: true,
headerTemplate: 'PGRpdiBzdHlsZT0id2lkdGg6IDEwMCU7IHRleHQtYWxpZ246IGNlbnRlcjsgZm9udC1zaXplOiAxMHB4OyBjb2xvcjogZ3JheTsiPjxzcGFuIGNsYXNzPSJ0aXRsZSI+PC9zcGFuPiB8IDxzcGFuIGNsYXNzPSJkYXRlIj48L3NwYW4+PC9kaXY+',
footerTemplate: 'PGRpdiBzdHlsZT0id2lkdGg6IDEwMCU7IHRleHQtYWxpZ246IGNlbnRlcjsgZm9udC1zaXplOiAxMnB4OyI+PHNwYW4gY2xhc3M9InBhZ2VOdW1iZXIiPjwvc3Bhbj4gb2YgPHNwYW4gY2xhc3M9InRvdGFsUGFnZXMiPjwvc3Bhbj48L2Rpdj4='
}
note

For a complete list of parameters and advanced options, visit the PDFBolt API Documentation.

9. Run Your PDF Generation Script

  1. Insert your actual PDFBolt API key in the generate-pdf.js file.
  2. Run the script using Node.js:
node generate-pdf.js

10. View Your Generated PDF

Check your project directory for the generated PDF file named invoice-[timestamp].pdf.

That's it! Simple, right? 🎉

Here's what your generated invoice PDF should look like:

Example invoice PDF generated with PDFBolt API showing professional formatting and layout

Troubleshooting Common HTML to PDF Conversion Issues

When working with HTML to PDF conversion, you might encounter some common issues:

1. Missing Styles or Images

Problem: The PDF is generated but styles or images are missing.

Solution:

  • Ensure all CSS is inline or within the HTML document.
  • Check that image URLs are absolute and publicly accessible.
  • Set printBackground: true in your API request.

2. Incorrect Page Sizes or Margins

Problem: Content is cut off or doesn't fit properly on the page.

Solution:

  • Use CSS @page rules to set consistent page dimensions.
  • Specify appropriate margin parameters in your API request.
  • Test with different format values (A4, Letter, etc.).

3. API Authentication Errors

Problem: Receiving 401 or 403 errors from the API.

Solution:

  • Double-check your API key.
  • Verify your account status and usage limits.
  • Ensure that the API-KEY header is included in your request.

4. Handling Large Documents

Problem: Timeout errors for large HTML documents.

Solution:

  • Break large documents into multiple smaller PDFs.
  • Optimize HTML content (reduce image sizes, simplify DOM).
  • Increase your request timeout parameter.
note

For more details, see the Error Handling Documentation.

Advanced Implementation Tips for HTML to PDF Conversion

For the best PDF quality, follow these best practices when designing your HTML templates:

  1. Use absolute units (mm, cm, in) instead of relative units (%, vh, vw) for predictable sizing.
  2. Set explicit page breaks with CSS page-break-before, page-break-after, and page-break-inside properties.
  3. Optimize images by using appropriate resolutions for print (300 DPI recommended).
  4. Test with different paper sizes to ensure your template adapts properly.

For applications requiring high-volume PDF generation:

  1. Consider webhooks for asynchronous processing of large documents.
  2. Utilize direct S3 uploads to avoid transferring large PDFs through your application servers. See our Uploading to Your S3 Bucket Guide for details.

When to Choose Different PDF Generation Methods

The right tool depends on what you are building:

  • HTML to PDF API (e.g. PDFBolt) - best when you want to skip infrastructure management and need consistent output at scale. Integration takes minutes, not days.

  • Headless browser (Puppeteer, Playwright) - best when you need full control over the page before capturing it, or when you must work offline. More setup and maintenance, but maximum flexibility.

  • wkhtmltopdf - a lightweight CLI tool for simple conversions. Note that wkhtmltopdf is deprecated and no longer maintained, so it is not recommended for new projects.

Additional Resources

Conclusion

Using an HTML to PDF API removes the need to manage headless browsers, handle rendering engine updates, or scale conversion infrastructure yourself. The Node.js example in this guide covers the full workflow: template, data, API call, and saved PDF. You can adapt the same pattern to any language that supports HTTP requests.

For more implementation examples, check the Quick Start Guide or explore the API documentation.

Keep calm and API on! 😎