Menu
Creating PDFs from HTML Templates
In this article
The following article steps through an example of how to create a PDF from an HTML Template.
HTML template example
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Estimate</title>
<style>
@media print {
* {
box-sizing: border-box;
}
html,
body {
width: 210mm;
height: 295mm;
}
body {
margin: 0;
padding: 20px 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 12px;
}
.color-bar {
height: 20px;
background: {{ primaryColor }};
}
.content-container {
padding: 28px 20px;
}
#header {
display: grid;
grid-template-columns: max-content auto max-content;
grid-gap: 20px;
background: #f3f3f3;
}
#header > div {
height: max-content;
position: relative;
top: 50%;
transform: translateY(-50%);
display: inline-block;
font-size: 13px;
}
.images img, .logo img{
width: 360px;
object-fit: contain;
}
#project {
display: grid;
grid-template-columns: auto 300px;
grid-gap: 20px;
}
.user-data-object-item,
.metadata-object-item,
.company-data-object-item {
display: grid;
grid-template-columns: 100px auto;
grid-gap: 10px;
margin-bottom: 10px;
}
.metadata-object-item {
grid-template-columns: 90px auto;
}
.user-data-object-item > div:nth-child(1),
.metadata-object-item > div:nth-child(1),
.company-data-object-item > div:nth-child(1) {
color: {{ primaryColor }};
}
.user-data-object-item > div:nth-child(2),
.metadata-object-item > div:nth-child(2),
.company-data-object-item > div:nth-child(2) {
text-transform: uppercase;
}
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
font-size: 12px;
}
td,
th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background: #f3f3f3;
}
}
</style>
</head>
<body>
<div>
<div class="color-bar"></div>
<div id="header" class="content-container">
<div class="logo">
<img src="{{ companyLogo }}" alt="logo" />
</div>
<div>{{ companyData }}</div>
<div>
<h1>ESTIMATE</h1>
<div>{{ metadata }}</div>
</div>
</div>
<div id="project" class="content-container">
<div class="images">
<img src="{{ thumbnail }}" alt="product" />
</div>
<div>
<h2>{{ heading }}</h2>
<hr />
{{ userData }}
</div>
</div>
<div id="table" class="content-container">{{ table }}</div>
</div>
<div class="color-bar"></div>
</body>
</html>
HTML
Copy
Create an HTML template by including dynamic content variables in curly braces.
Generating dynamic HTML example:
const generateItems = itemType => content => {
const generateItem = val =>
Array.isArray(val)
? `<div class="${itemType}-object-item"><div>${val[0]}</div><div>${val[1]}</div></div>`
: `<div class="${itemType}-array-item">${val}</div>`;
return Array.isArray(content)
? content.reduce((output, item) => {
output += generateItem(item);
return output;
}, '')
: Object.entries(content).reduce((output, item) => {
output += generateItem(item);
return output;
}, '');
};
const generateUserDataHtml = generateItems('user-data');
const generateCompanyDataHtml = generateItems('company-data');
const generateMetadataHtml = generateItems('metadata');
const generateTableHtml = rows => {
if (!rows) return new Error('missing rows data');
let tableHeader = `<table>
<tr>
<th>Description</th>
<th style="text-align: center;">Price</th>
<th style="text-align: center;">Quantity</th>
<th style="text-align: center;">Total</th>
</tr>`;
let tableFooter = `</table>`;
const generateTableRow = (description, price, quantity) => `<tr>
<td>${description}</td>
<td style="text-align: center;">$${price}</td>
<td style="text-align: center;">${quantity}</td>
<td style="text-align: center;">$${price * quantity}</td>
</tr>`;
rows.forEach(
row =>
(tableHeader += generateTableRow(
row.description,
row.price,
row.quantity
))
);
return tableHeader + tableFooter;
};
export const generateHtml = (data, pageTemplate) => {
if (!data) new Error('data missing');
let html = pageTemplate;
const companyData = generateCompanyDataHtml(data.companyData);
const metadata = generateMetadataHtml(data.metadata);
const userData = generateUserDataHtml(data.userData);
const table = generateTableHtml(data.table);
Object.entries({
...data,
companyData,
metadata,
userData,
table,
}).forEach(([key, val]) => {
const re = new RegExp(`{{ ${key} }}`, 'g');
html = html.replace(re, val);
});
return html;
};
JavaScript
Copy
Create Logic
Create logic to replace the dynamic content variables with order-specific HTML using regex matching of request data keys and dynamic content variables.
Create PDF from HTML:
import fs from 'fs';
import puppeteer from 'puppeteer';
export const htmlToPdf = htmlContent => {
return new Promise(async (resolve, reject) => {
try {
const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: 'networkidle0' });
await page.emulateMediaType('print');
const byteArray = await page.pdf({
format: 'A4',
printBackground: true,
});
const buffer = Buffer.from(byteArray, 'binary');
browser.close();
resolve(buffer);
} catch (err) {
reject(err);
}
});
};
JavaScript
Copy
Create a PDF from the generated HTML using Puppeteer.
Server example:
import express from 'express';
import cors from 'cors';
import path from 'path';
import morgan from 'morgan';
import bodyParser from 'body-parser';
import fs from 'fs';
import { PORT } from './constants.mjs';
import { generateHtml, htmlToPdf } from './utils.mjs';
const app = express();
const __dirname = path.dirname(new URL(import.meta.url).pathname);
app.use(morgan('tiny'));
app.use(cors());
app.use(express.static(path.join(__dirname, '../build')));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(
bodyParser.urlencoded({
limit: '50mb',
extended: true,
parameterLimit: 50000,
})
);
app.post('/pdf', async (req, res) => {
const { templateVersion, ...data } = req.body;
const htmlTemplate = fs.readFileSync(
path.join(
__dirname,
`./pdf-templates/${templateVersion || 'template1'}`,
'pdf.html'
),
'utf8'
);
const html = generateHtml(data, htmlTemplate);
const pdf = await htmlToPdf(html);
res.set({
'Content-type': 'application/pdf',
});
res.end(pdf);
});
app.listen(PORT, () => console.log('listening on port: ', PORT));
JavaScript
Copy
Generate, Create, and Output the PDF
Using the template version specified in the request, generate the HTML, create a PDF from the HTML, and output the PDF.
Request example:
{
"primaryColor": "#4a7f9e",
"companyLogo": "https://logo.svg",
"companyData": ["Phone: (012) 345-6789", "E-mail: company@email.com"],
"heading": "Contact Information",
"metadata": {
"projectName": "New Product",
"orderNumber": "12345678"
},
"userData": {
"Name": "Some One",
"Address": "123 Metcalfe St - Apt 456, Ottawa ON",
"Phone": "(613) 555-1234",
"Email": "someone@email.com"
},
"table": [
{
"description": "Item 1",
"quantity": 1,
"price": 200
},
{
"description": "Item 2",
"quantity": 4,
"price": 100
},
{
"description": "Item 3",
"quantity": 1,
"price": 400
}
],
"thumbnail": "https://preview.threekit.com/api/files/uuid/content",
"templateVersion": "template1"
}
JavaScript
Copy
Output example: