Advanced PDF Features: Headers, Footers, Watermarks, and Page Numbers That Actually Work š
May 30, 2025
You've mastered basic PDF generation. Your invoices look great.
But now you need:
- Page numbers ("Page 3 of 12")
- Running headers with dynamic content
- Watermarks for draft documents
- Table of contents with clickable links
- Custom page sizes and margins
And suddenly, what seemed simple gets complicated fast.
This guide shows you how to implement advanced PDF features that make your documents look professional - with actual working code.
Feature 1: Headers and Footers
The Challenge
Headers and footers need to appear on every page with dynamic content (page numbers, dates, etc.).
Basic Header with CSS
<style>
@page {
margin-top: 80px;
margin-bottom: 80px;
@top-center {
content: "CONFIDENTIAL INVOICE";
font-size: 10px;
color: #64748b;
}
@bottom-left {
content: "Company Name ⢠example.com";
font-size: 9px;
color: #94a3b8;
}
@bottom-right {
content: "Page " counter(page) " of " counter(pages);
font-size: 9px;
color: #94a3b8;
}
}
body {
font-family: 'Inter', sans-serif;
}
</style>
<body>
<h1>Invoice #12345</h1>
<p>Your invoice content here...</p>
</body>Advanced Header with Logo and Dynamic Content
<style>
@page {
margin-top: 120px;
margin-bottom: 100px;
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100px;
padding: 20px 40px;
border-bottom: 2px solid #e5e7eb;
background: white;
}
.header-content {
display: table;
width: 100%;
}
.header-logo {
display: table-cell;
width: 150px;
}
.header-info {
display: table-cell;
text-align: right;
vertical-align: middle;
font-size: 11px;
color: #64748b;
}
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 80px;
padding: 20px 40px;
border-top: 1px solid #e5e7eb;
background: white;
}
.footer-content {
display: table;
width: 100%;
}
.footer-left {
display: table-cell;
font-size: 9px;
color: #94a3b8;
}
.footer-right {
display: table-cell;
text-align: right;
font-size: 9px;
color: #94a3b8;
}
.page-number:before {
content: counter(page);
}
.total-pages:before {
content: counter(pages);
}
</style>
<div class="header">
<div class="header-content">
<div class="header-logo">
<img src="https://yourdomain.com/logo.png" width="120">
</div>
<div class="header-info">
Invoice #{{invoice_number}}<br>
Date: {{formatted_date}}
</div>
</div>
</div>
<div class="footer">
<div class="footer-content">
<div class="footer-left">
{{company_name}} ⢠{{company_address}}<br>
{{company_email}} ⢠{{company_phone}}
</div>
<div class="footer-right">
Page <span class="page-number"></span> of <span class="total-pages"></span>
</div>
</div>
</div>
<body>
<!-- Your content here -->
</body>Different Headers for First Page
<style>
@page {
margin-top: 120px;
}
@page :first {
margin-top: 0; /* No header on first page */
}
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100px;
}
/* Hide header on first page */
.first-page .header {
display: none;
}
</style>Feature 2: Page Numbers with Custom Formatting
Simple Page Numbers
<style>
.page-number:after {
content: counter(page);
}
</style>
<footer>
Page <span class="page-number"></span>
</footer>"Page X of Y" Format
<style>
.current-page:after {
content: counter(page);
}
.total-pages:after {
content: counter(pages);
}
</style>
<footer>
Page <span class="current-page"></span> of <span class="total-pages"></span>
</footer>Custom Page Number Styling
<style>
.page-counter {
position: fixed;
bottom: 20px;
right: 40px;
background: #2563eb;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
}
.page-counter:after {
content: counter(page) " / " counter(pages);
}
</style>
<div class="page-counter"></div>Section-Based Page Numbers
<style>
/* Reset page counter for appendix */
.appendix {
counter-reset: page 1;
}
.appendix .page-number:before {
content: "A-" counter(page);
}
</style>
<div class="main-content">
<p>Main content with page numbers 1, 2, 3...</p>
</div>
<div class="appendix">
<h1>Appendix</h1>
<p>Appendix pages numbered A-1, A-2, A-3...</p>
</div>Feature 3: Watermarks
Draft Watermark
<style>
.watermark {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-size: 120px;
font-weight: 900;
color: rgba(239, 68, 68, 0.1);
z-index: -1;
pointer-events: none;
}
</style>
<div class="watermark">DRAFT</div>
<body>
<!-- Your content here -->
</body>Conditional Watermark
// Server-side logic
function generateInvoice(invoiceData) {
const watermark = invoiceData.status === 'draft'
? '<div class="watermark">DRAFT</div>'
: '';
const html = `
<style>
.watermark {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-size: 100px;
font-weight: 900;
color: rgba(239, 68, 68, 0.15);
z-index: -1;
}
</style>
${watermark}
<body>
<!-- Invoice content -->
</body>
`;
return generatePDF(html);
}Image Watermark
<style>
.watermark-image {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0.05;
z-index: -1;
width: 600px;
}
</style>
<img src="https://yourdomain.com/logo-watermark.png" class="watermark-image">Per-Page Watermark
<style>
@page {
background-image: url('https://yourdomain.com/watermark.png');
background-repeat: no-repeat;
background-position: center;
background-size: 50%;
background-opacity: 0.1;
}
</style>Feature 4: Page Breaks
Explicit Page Breaks
<style>
.page-break {
page-break-after: always;
}
</style>
<div>
<h1>Section 1</h1>
<p>Content for section 1...</p>
</div>
<div class="page-break"></div>
<div>
<h1>Section 2</h1>
<p>Content for section 2 on new page...</p>
</div>Avoid Breaking Elements
<style>
.keep-together {
page-break-inside: avoid;
}
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
</style>
<div class="keep-together">
<h2>Important Section</h2>
<p>This content will not be split across pages.</p>
</div>Orphans and Widows
<style>
p {
orphans: 3; /* Minimum 3 lines at bottom of page */
widows: 3; /* Minimum 3 lines at top of page */
}
</style>Feature 5: Table of Contents with Links
Generate TOC with JavaScript
function generateTableOfContents(sections) {
return sections.map((section, index) => `
<div class="toc-item">
<a href="#section-${index}">${section.title}</a>
<span class="toc-page">${section.page}</span>
</div>
`).join('');
}
const sections = [
{ title: 'Executive Summary', page: 2 },
{ title: 'Financial Overview', page: 5 },
{ title: 'Market Analysis', page: 12 },
{ title: 'Appendix', page: 20 }
];
const html = `
<style>
.toc-item {
display: table;
width: 100%;
margin-bottom: 8px;
}
.toc-item a {
display: table-cell;
text-decoration: none;
color: #1e293b;
}
.toc-item a:hover {
color: #2563eb;
}
.toc-page {
display: table-cell;
width: 40px;
text-align: right;
color: #64748b;
}
</style>
<h1>Table of Contents</h1>
${generateTableOfContents(sections)}
<div class="page-break"></div>
<h1 id="section-0">Executive Summary</h1>
<!-- Content -->
<div class="page-break"></div>
<h1 id="section-1">Financial Overview</h1>
<!-- Content -->
`;Styled TOC
<style>
.toc {
margin: 40px 0;
}
.toc-title {
font-size: 24px;
font-weight: 700;
margin-bottom: 20px;
color: #1e293b;
}
.toc-section {
margin-bottom: 16px;
padding-left: 0;
}
.toc-section.level-2 {
padding-left: 20px;
font-size: 14px;
}
.toc-section.level-3 {
padding-left: 40px;
font-size: 12px;
color: #64748b;
}
.toc-link {
display: table;
width: 100%;
text-decoration: none;
color: inherit;
}
.toc-text {
display: table-cell;
}
.toc-leader {
display: table-cell;
width: 100%;
border-bottom: 1px dotted #cbd5e1;
}
.toc-page {
display: table-cell;
width: 40px;
text-align: right;
padding-left: 8px;
}
</style>
<div class="toc">
<h1 class="toc-title">Table of Contents</h1>
<div class="toc-section level-1">
<a href="#intro" class="toc-link">
<span class="toc-text">1. Introduction</span>
<span class="toc-leader"></span>
<span class="toc-page">1</span>
</a>
</div>
<div class="toc-section level-2">
<a href="#overview" class="toc-link">
<span class="toc-text">1.1 Overview</span>
<span class="toc-leader"></span>
<span class="toc-page">2</span>
</a>
</div>
<div class="toc-section level-2">
<a href="#scope" class="toc-link">
<span class="toc-text">1.2 Scope</span>
<span class="toc-leader"></span>
<span class="toc-page">3</span>
</a>
</div>
</div>Feature 6: Custom Page Sizes and Margins
Standard Page Sizes
<style>
/* A4 (default) */
@page {
size: A4;
margin: 20mm;
}
/* Letter */
@page letter {
size: letter;
margin: 1in;
}
/* Legal */
@page legal {
size: legal;
margin: 1in;
}
/* A3 */
@page large {
size: A3;
margin: 30mm;
}
</style>Custom Page Dimensions
<style>
@page custom {
size: 8.5in 11in; /* Width Ć Height */
margin: 0.75in 1in;
}
</style>Landscape Orientation
<style>
@page landscape {
size: A4 landscape;
margin: 15mm;
}
.landscape-section {
page: landscape;
}
</style>
<div>
<p>This is portrait...</p>
</div>
<div class="landscape-section">
<table>
<!-- Wide table that needs landscape -->
</table>
</div>
<div>
<p>Back to portrait...</p>
</div>Different Margins for Different Sections
<style>
@page normal {
margin: 20mm;
}
@page wide {
margin: 10mm; /* Narrower margins for more content */
}
.wide-content {
page: wide;
}
</style>
<div>
<p>Normal margins...</p>
</div>
<div class="wide-content">
<table>
<!-- Wide table with narrow margins -->
</table>
</div>Feature 7: Background Images and Patterns
Full-Page Background
<style>
@page {
background-image: url('https://yourdomain.com/letterhead.png');
background-size: cover;
background-repeat: no-repeat;
}
</style>Repeating Pattern Background
<style>
body {
background-image: url('https://yourdomain.com/pattern.png');
background-size: 50px 50px;
background-repeat: repeat;
background-opacity: 0.05;
}
</style>Background Only on First Page
<style>
@page :first {
background-image: url('https://yourdomain.com/cover-background.jpg');
background-size: cover;
}
</style>Feature 8: Bookmarks and Outline
Create PDF Outline/Bookmarks
// Using pdf-lib to add bookmarks
import { PDFDocument } from 'pdf-lib';
async function addBookmarks(pdfBytes, bookmarks) {
const pdfDoc = await PDFDocument.load(pdfBytes);
// Create outline (bookmarks)
const outline = pdfDoc.context.obj({
Type: 'Outlines',
Count: bookmarks.length
});
// Add bookmarks
bookmarks.forEach((bookmark, index) => {
const dest = pdfDoc.context.obj([
pdfDoc.getPage(bookmark.page).ref,
'XYZ',
null,
null,
null
]);
const item = pdfDoc.context.obj({
Title: bookmark.title,
Parent: outline,
Dest: dest
});
outline.set('First', item);
outline.set('Last', item);
});
pdfDoc.catalog.set('Outlines', outline);
return await pdfDoc.save();
}
// Usage
const bookmarks = [
{ title: 'Introduction', page: 0 },
{ title: 'Chapter 1', page: 5 },
{ title: 'Chapter 2', page: 12 },
{ title: 'Conclusion', page: 20 }
];
const pdfWithBookmarks = await addBookmarks(originalPdf, bookmarks);Feature 9: Form Fields (Fillable PDFs)
Add Form Fields with pdf-lib
import { PDFDocument, PDFTextField, PDFCheckBox } from 'pdf-lib';
async function createFillableForm() {
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([600, 800]);
const form = pdfDoc.getForm();
// Text field
const nameField = form.createTextField('customer.name');
nameField.addToPage(page, {
x: 50,
y: 700,
width: 200,
height: 30
});
// Checkbox
const agreeCheckbox = form.createCheckBox('terms.agree');
agreeCheckbox.addToPage(page, {
x: 50,
y: 650,
width: 20,
height: 20
});
return await pdfDoc.save();
}Feature 10: QR Codes and Barcodes
Embed QR Code
import QRCode from 'qrcode';
async function generateInvoiceWithQR(invoiceData) {
// Generate QR code as data URL
const qrCodeUrl = await QRCode.toDataURL(
`https://yourdomain.com/invoice/${invoiceData.id}`,
{ width: 200 }
);
const html = `
<style>
.qr-code {
position: fixed;
bottom: 20px;
right: 20px;
width: 150px;
height: 150px;
}
</style>
<img src="${qrCodeUrl}" class="qr-code" alt="Invoice QR Code">
<body>
<!-- Invoice content -->
</body>
`;
return generatePDF(html);
}Embed Barcode
import JsBarcode from 'jsbarcode';
import { createCanvas } from 'canvas';
function generateBarcode(value) {
const canvas = createCanvas();
JsBarcode(canvas, value, {
format: 'CODE128',
width: 2,
height: 50,
displayValue: true
});
return canvas.toDataURL();
}
// In template
const barcodeUrl = generateBarcode('INV-12345');
const html = `
<img src="${barcodeUrl}" alt="Invoice Barcode">
`;Complete Advanced Invoice Example
async function generateAdvancedInvoice(invoiceData) {
const qrCodeUrl = await QRCode.toDataURL(
`https://yourdomain.com/invoice/${invoiceData.id}`
);
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
@page {
size: A4;
margin: 120px 40px 100px 40px;
}
/* Header */
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 100px;
padding: 20px 40px;
border-bottom: 2px solid #e5e7eb;
}
.header-logo {
float: left;
width: 150px;
}
.header-info {
float: right;
text-align: right;
font-size: 11px;
color: #64748b;
}
/* Footer */
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20px 40px;
border-top: 1px solid #e5e7eb;
font-size: 9px;
color: #94a3b8;
}
.footer-left {
float: left;
}
.footer-right {
float: right;
}
.page-number:after {
content: counter(page) " of " counter(pages);
}
/* Watermark for drafts */
${invoiceData.status === 'draft' ? `
.watermark {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
font-size: 100px;
font-weight: 900;
color: rgba(239, 68, 68, 0.1);
z-index: -1;
}
` : ''}
/* QR Code */
.qr-code {
position: fixed;
bottom: 20px;
right: 20px;
width: 120px;
}
/* Content */
body {
font-family: 'Inter', sans-serif;
}
.invoice-title {
font-size: 32px;
font-weight: 700;
color: #1e293b;
margin-bottom: 20px;
}
/* Keep tables together */
table {
page-break-inside: auto;
}
tr {
page-break-inside: avoid;
}
</style>
</head>
<body>
<!-- Header -->
<div class="header">
<img src="https://yourdomain.com/logo.png" class="header-logo">
<div class="header-info">
Invoice #${invoiceData.number}<br>
Date: ${invoiceData.formatted_date}
</div>
</div>
<!-- Footer -->
<div class="footer">
<div class="footer-left">
${invoiceData.company_name} ⢠${invoiceData.company_email}
</div>
<div class="footer-right">
Page <span class="page-number"></span>
</div>
</div>
<!-- Watermark (if draft) -->
${invoiceData.status === 'draft' ? '<div class="watermark">DRAFT</div>' : ''}
<!-- QR Code -->
<img src="${qrCodeUrl}" class="qr-code">
<!-- Content -->
<h1 class="invoice-title">Invoice</h1>
<!-- Invoice content here -->
</body>
</html>
`;
return generatePDF(html);
}Final Thoughts
Advanced PDF features transform basic documents into professional, polished outputs.
Master these techniques:
- ā Headers and footers with dynamic content
- ā Page numbers with custom formatting
- ā Watermarks for drafts/confidential docs
- ā Smart page breaks
- ā Table of contents with links
- ā Custom page sizes and orientations
- ā QR codes and barcodes
Your PDFs will stand out. Your users will notice.
Ready for advanced PDF generation? Try reportgen.io - supports all these features out of the box.