Basic Puppeteer PDF
npm install puppeteer
const puppeteer = require('puppeteer');
async function urlToPdf(url, outputPath) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true, // Include CSS backgrounds
margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' }
});
await browser.close();
console.log(`PDF saved: ${outputPath}`);
}
urlToPdf('https://example.com', 'output.pdf');
PDF Options Reference
await page.pdf({
path: 'output.pdf', // Omit to get Buffer instead of file
format: 'A4', // 'Letter', 'Legal', 'A3', 'A4', 'A5', etc.
landscape: false, // true = horizontal
printBackground: true, // Include CSS backgrounds and colors
margin: {
top: '2cm',
right: '1.5cm',
bottom: '2cm',
left: '1.5cm'
},
scale: 1.0, // 0.1–2.0, affects zoom level
pageRanges: '', // '' = all, '1-3' = first 3 pages only
preferCSSPageSize: false // Use @page CSS size instead of format param
});
Custom Headers and Footers
Puppeteer supports HTML headers and footers with special classes for page numbers and dates.
await page.pdf({
format: 'A4',
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `
Company Confidential
`,
footerTemplate: `
Generated by SnapAPI
Page of
`,
margin: { top: '2cm', bottom: '2cm', left: '1cm', right: '1cm' }
});
Special CSS classes for dynamic content in templates:
.date— current date/time.title— page title from<title>.url— current URL.pageNumber— current page number.totalPages— total page count
Generate PDF from Raw HTML
const puppeteer = require('puppeteer');
async function htmlToPdf(html, outputPath) {
const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox'] });
const page = await browser.newPage();
// Set HTML content directly — no URL needed
await page.setContent(html, { waitUntil: 'networkidle2' });
// Inject print-specific CSS
await page.addStyleTag({
content: `
@page { size: A4; margin: 2cm; }
body { font-family: Arial, sans-serif; font-size: 12pt; }
.no-print { display: none !important; }
h1 { page-break-before: avoid; }
table { page-break-inside: avoid; }
`
});
const pdf = await page.pdf({ format: 'A4', printBackground: true });
require('fs').writeFileSync(outputPath, pdf);
await browser.close();
return pdf;
}
const html = `
Invoice #1042
Amount due: $299.00
Item Qty Price
SnapAPI Pro 1 $79
`;
htmlToPdf(html, 'invoice.pdf');
Multi-Page CSS for Clean Breaks
// Inject CSS to control page breaks before generating PDF
await page.addStyleTag({
content: `
/* Avoid breaking inside these elements */
tr, img, pre, blockquote, figure { page-break-inside: avoid; }
/* Force a new page before these */
h1, .page-break-before { page-break-before: always; }
/* Keep heading with the paragraph that follows */
h2, h3 { page-break-after: avoid; }
/* Remove shadows/borders that look bad in print */
* { box-shadow: none !important; }
/* Ensure links are visible in print */
a { text-decoration: underline; color: #0066cc; }
`
});
const pdf = await page.pdf({ format: 'A4', printBackground: true });
PDF from Authenticated Pages
const puppeteer = require('puppeteer');
async function authenticatedPdf(loginUrl, credentials, targetUrl, outputPath) {
const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox'] });
const page = await browser.newPage();
// Step 1: Log in
await page.goto(loginUrl, { waitUntil: 'networkidle2' });
await page.type('#email', credentials.email);
await page.type('#password', credentials.password);
await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle2' }),
page.click('[type="submit"]')
]);
// Step 2: Generate PDF from authenticated page
await page.goto(targetUrl, { waitUntil: 'networkidle2' });
await page.pdf({ path: outputPath, format: 'A4', printBackground: true });
await browser.close();
}
authenticatedPdf(
'https://app.example.com/login',
{ email: 'user@co.com', password: process.env.APP_PASS },
'https://app.example.com/report/2026-Q1',
'q1-report.pdf'
);
Express PDF Download Endpoint
const express = require('express');
const puppeteer = require('puppeteer');
const app = express();
app.use(express.json());
let browser;
(async () => { browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox'] }); })();
app.post('/pdf', async (req, res) => {
const { url, html: rawHtml, format = 'A4' } = req.body;
if (!url && !rawHtml) return res.status(400).json({ error: 'url or html required' });
try {
const page = await browser.newPage();
if (rawHtml) {
await page.setContent(rawHtml, { waitUntil: 'networkidle2' });
} else {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
}
const pdf = await page.pdf({ format, printBackground: true });
await page.close();
res.set({
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="document.pdf"',
'Content-Length': pdf.length
});
res.send(pdf);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(3000);
// POST /pdf with { "url": "https://example.com" } → downloads PDF
REST API Alternative (No Browser)
On Lambda, Vercel, or Railway, Puppeteer's Chromium binary causes size and cold-start problems. A PDF API eliminates the browser dependency:
// SnapAPI PDF — works on any hosting, no binary needed
const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': process.env.SNAPAPI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
url: 'https://example.com',
format: 'pdf',
full_page: true,
wait_for: 'networkidle'
})
});
const { pdf } = await response.json();
// pdf = base64-encoded PDF
const pdfBuffer = Buffer.from(pdf, 'base64');
require('fs').writeFileSync('output.pdf', pdfBuffer);
// HTML → PDF via API (no Chromium needed)
const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: { 'X-Api-Key': process.env.SNAPAPI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
html: '<html><body><h1>Invoice</h1></body></html>',
format: 'pdf',
width: 794, // A4 at 96dpi
height: 1123
})
});
const { pdf } = await response.json();
Serverless PDF: SnapAPI works on Lambda, Vercel, Cloudflare Workers — just a fetch() call. Free tier: 200 calls/month. Get key →
Puppeteer vs API: When to Use Which
| Scenario | Puppeteer | SnapAPI |
|---|---|---|
| Self-hosted VPS | ✅ free | ✅ |
| AWS Lambda / Vercel | ⚠️ layer workarounds | ✅ native |
| Docker container | ⚠️ +500MB image | ✅ no binary |
| Custom headers/footers | ✅ full control | ✅ |
| Auth pages | ✅ full control | ✅ via headers |
| High concurrency (>10 concurrent) | ⚠️ memory pressure | ✅ managed |