reportgen
Back to all posts

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.