Guides
Generate PDFs from web pages
Convert web pages to PDF documents programmatically
Generate PDFs from web pages
Convert any web page to a PDF document with full styling, images, and formatting preserved.
Use cases
- Invoice generation: Convert invoice pages to downloadable PDFs
- Report exports: Generate PDF reports from dashboards
- Documentation: Create offline documentation from web docs
- Contracts: Generate signed agreement PDFs
- Receipts: Create transaction receipts
Basic PDF generation
async function generatePdf(url) {
const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url,
format: 'pdf',
fullPage: true,
waitUntil: 'networkidle',
}),
});
return response.blob();
}
// Usage
const pdf = await generatePdf('https://example.com/invoice/123');Invoice generation workflow
Create an invoice template
Design a web page optimized for PDF output:
// pages/invoice/[id].jsx
export default function InvoicePage({ invoice }) {
return (
<div className="invoice" style={{
width: '210mm', // A4 width
minHeight: '297mm', // A4 height
padding: '20mm',
fontFamily: 'system-ui, sans-serif',
}}>
<header style={{ marginBottom: '40px' }}>
<img src="/logo.png" alt="Company" style={{ height: '50px' }} />
<div style={{ textAlign: 'right' }}>
<h1>INVOICE</h1>
<p>#{invoice.number}</p>
<p>{formatDate(invoice.date)}</p>
</div>
</header>
<section style={{ marginBottom: '40px' }}>
<h2>Bill To:</h2>
<p>{invoice.customer.name}</p>
<p>{invoice.customer.address}</p>
<p>{invoice.customer.email}</p>
</section>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ borderBottom: '2px solid #333' }}>
<th style={{ textAlign: 'left', padding: '10px' }}>Description</th>
<th style={{ textAlign: 'right', padding: '10px' }}>Qty</th>
<th style={{ textAlign: 'right', padding: '10px' }}>Price</th>
<th style={{ textAlign: 'right', padding: '10px' }}>Total</th>
</tr>
</thead>
<tbody>
{invoice.items.map(item => (
<tr key={item.id} style={{ borderBottom: '1px solid #eee' }}>
<td style={{ padding: '10px' }}>{item.description}</td>
<td style={{ textAlign: 'right', padding: '10px' }}>{item.quantity}</td>
<td style={{ textAlign: 'right', padding: '10px' }}>${item.price}</td>
<td style={{ textAlign: 'right', padding: '10px' }}>${item.total}</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan="3" style={{ textAlign: 'right', padding: '10px', fontWeight: 'bold' }}>
Total:
</td>
<td style={{ textAlign: 'right', padding: '10px', fontWeight: 'bold' }}>
${invoice.total}
</td>
</tr>
</tfoot>
</table>
<footer style={{ marginTop: '60px', borderTop: '1px solid #eee', paddingTop: '20px' }}>
<p>Payment due within 30 days</p>
<p>Bank: {invoice.bankDetails}</p>
</footer>
</div>
);
}Generate the PDF
// API route: /api/invoices/[id]/pdf
export async function GET(request, { params }) {
const invoice = await db.invoices.findUnique({
where: { id: params.id },
include: { customer: true, items: true },
});
if (!invoice) {
return new Response('Invoice not found', { status: 404 });
}
const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: `${process.env.APP_URL}/invoice/${params.id}`,
format: 'pdf',
fullPage: true,
viewport: {
width: 794, // A4 at 96 DPI
height: 1123,
},
waitUntil: 'networkidle',
}),
});
const pdf = await response.arrayBuffer();
return new Response(pdf, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="invoice-${invoice.number}.pdf"`,
},
});
}Add download button
function InvoiceActions({ invoiceId }) {
const downloadPdf = async () => {
const response = await fetch(`/api/invoices/${invoiceId}/pdf`);
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `invoice-${invoiceId}.pdf`;
a.click();
URL.revokeObjectURL(url);
};
return (
<button onClick={downloadPdf}>
Download PDF
</button>
);
}PDF styling tips
Print-optimized CSS
@media print {
/* Hide non-printable elements */
.no-print, nav, .sidebar {
display: none !important;
}
/* Avoid page breaks inside elements */
.keep-together {
page-break-inside: avoid;
}
/* Force page break before element */
.page-break {
page-break-before: always;
}
/* Remove backgrounds for cleaner print */
body {
background: white !important;
}
}Inject print styles via API
const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: pageUrl,
format: 'pdf',
fullPage: true,
customCss: `
@page {
margin: 20mm;
}
body {
font-size: 12pt;
line-height: 1.5;
}
.no-print {
display: none !important;
}
table {
page-break-inside: avoid;
}
`,
}),
});Multi-page reports
Generate reports that span multiple pages:
// Report template with multiple sections
function ReportTemplate({ data }) {
return (
<div className="report">
{/* Cover page */}
<section className="page cover">
<h1>{data.title}</h1>
<p>{data.date}</p>
</section>
{/* Executive summary */}
<section className="page summary">
<h2>Executive Summary</h2>
<p>{data.summary}</p>
</section>
{/* Data sections */}
{data.sections.map(section => (
<section key={section.id} className="page">
<h2>{section.title}</h2>
{section.content}
{section.chart && <Chart data={section.chart} />}
</section>
))}
<style jsx>{`
.page {
min-height: 297mm;
padding: 20mm;
page-break-after: always;
}
.page:last-child {
page-break-after: auto;
}
`}</style>
</div>
);
}Async PDF generation for large documents
For large documents, use async jobs:
async function generateLargeReport(reportId) {
// Start async job
const response = await fetch('https://api.allscreenshots.com/v1/screenshots/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ALLSCREENSHOTS_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: `${process.env.APP_URL}/reports/${reportId}`,
format: 'pdf',
fullPage: true,
timeout: 60000, // Allow longer timeout for complex reports
webhookUrl: `${process.env.APP_URL}/webhooks/pdf-ready`,
}),
});
const { jobId } = await response.json();
// Store job ID for later reference
await db.reports.update({
where: { id: reportId },
data: { pdfJobId: jobId, pdfStatus: 'generating' },
});
return jobId;
}
// Webhook handler
app.post('/webhooks/pdf-ready', async (req, res) => {
const { jobId, status, result } = req.body;
const report = await db.reports.findFirst({
where: { pdfJobId: jobId },
});
if (status === 'completed') {
await db.reports.update({
where: { id: report.id },
data: { pdfUrl: result.url, pdfStatus: 'ready' },
});
// Notify user
await sendEmail(report.userEmail, {
subject: 'Your report is ready',
body: `Download your report: ${result.url}`,
});
}
res.sendStatus(200);
});Best practices
Use fixed widths (like A4: 794px at 96 DPI) for predictable PDF layouts.
Font handling
Ensure fonts are loaded before capture:
const response = await fetch('https://api.allscreenshots.com/v1/screenshots', {
body: JSON.stringify({
url: pageUrl,
format: 'pdf',
waitUntil: 'networkidle',
delay: 500, // Extra time for fonts to load
}),
});Image optimization
- Use absolute URLs for images
- Ensure images are loaded before capture
- Consider embedding base64 images for reliability
Page size reference
| Paper Size | Width (px) | Height (px) | At DPI |
|---|---|---|---|
| A4 | 794 | 1123 | 96 |
| Letter | 816 | 1056 | 96 |
| Legal | 816 | 1344 | 96 |
| A4 (print) | 595 | 842 | 72 |