Get started

How to add page numbers to a PDF (free, online, no install)

Add page numbers to any PDF in seconds. Free online tool, no upload required. Also covers programmatic methods using Python, Node.js, and pdf-lib.

benoitdedMarch 27, 202610 min read

Adding page numbers to a PDF takes under 30 seconds with the right tool. Use the PDF4.dev number PDF tool for an instant browser-based solution — no upload, no install. For programmatic use in Node.js or Python, this article covers working code examples.

The quick way: free online tool

The PDF4.dev number PDF tool adds page numbers client-side in your browser. Your file never leaves your device, which matters when working with confidential documents like contracts or financial reports.

Steps:

  1. Go to pdf4.dev/tools/number-pdf
  2. Drop your PDF onto the upload area
  3. Choose position (bottom center, bottom right, top right, etc.)
  4. Set font size (10px is standard for most documents)
  5. Choose starting number (default: 1)
  6. Click "Add page numbers" and download

The tool works on any modern browser — Chrome, Firefox, Safari, Edge — with no size limit for most documents.

Page number position guide

The position of page numbers affects how professional a document looks and how it behaves when printed.

Use caseRecommended positionFormat
Standard report or articleBottom center1, 2, 3
Double-sided print (book)Bottom outer (alt. left/right)Page X of Y
Legal documentsBottom center or bottom right1, 2, 3
Academic thesisBottom centeri, ii, iii (front matter) then 1, 2, 3
Bates-numbered exhibitsBottom rightPREFIX-000001
Slide deck printed as handoutTop rightPage X of Y

For most general-purpose documents, bottom center at 10-11pt is the safe default.

Add page numbers in Node.js with pdf-lib

pdf-lib is a pure JavaScript library that reads and writes PDF files in Node.js without any native dependencies or Chromium binary.

import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
import { readFileSync, writeFileSync } from "fs";
 
async function addPageNumbers(inputPath: string, outputPath: string) {
  const existingPdfBytes = readFileSync(inputPath);
  const pdfDoc = await PDFDocument.load(existingPdfBytes);
  const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
  const pages = pdfDoc.getPages();
  const totalPages = pages.length;
 
  pages.forEach((page, index) => {
    const { width, height } = page.getSize();
    const pageNumber = index + 1;
    const text = `${pageNumber} / ${totalPages}`;
    const fontSize = 10;
    const textWidth = font.widthOfTextAtSize(text, fontSize);
 
    page.drawText(text, {
      x: (width - textWidth) / 2, // centered horizontally
      y: 20,                       // 20pt from bottom
      size: fontSize,
      font,
      color: rgb(0.4, 0.4, 0.4),  // gray
    });
  });
 
  const pdfBytes = await pdfDoc.save();
  writeFileSync(outputPath, pdfBytes);
  console.log(`Added page numbers to ${totalPages} pages → ${outputPath}`);
}
 
addPageNumbers("input.pdf", "output-numbered.pdf");

Install pdf-lib with:

npm install pdf-lib

The library is pure JavaScript — no native addons, no Chromium. It works in Node.js, browsers, Deno, and any JavaScript runtime.

Add page numbers in Python

Python has two common approaches: pypdf for reading existing PDFs and overlaying text, or reportlab for generating PDFs with page numbers from scratch.

import io
from pypdf import PdfReader, PdfWriter
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
 
def add_page_numbers(input_path: str, output_path: str) -> None:
    reader = PdfReader(input_path)
    writer = PdfWriter()
    total_pages = len(reader.pages)
 
    for i, page in enumerate(reader.pages):
        # Get page dimensions
        width = float(page.mediabox.width)
        height = float(page.mediabox.height)
 
        # Create an overlay PDF with just the page number
        packet = io.BytesIO()
        c = canvas.Canvas(packet, pagesize=(width, height))
        c.setFont("Helvetica", 10)
        c.setFillColorRGB(0.4, 0.4, 0.4)
 
        text = f"{i + 1} / {total_pages}"
        text_width = c.stringWidth(text, "Helvetica", 10)
        c.drawString((width - text_width) / 2, 20, text)
        c.save()
 
        # Merge the overlay onto the existing page
        packet.seek(0)
        overlay_pdf = PdfReader(packet)
        page.merge_page(overlay_pdf.pages[0])
        writer.add_page(page)
 
    with open(output_path, "wb") as f:
        writer.write(f)
 
    print(f"Added page numbers to {total_pages} pages → {output_path}")
 
add_page_numbers("input.pdf", "output-numbered.pdf")

Install dependencies:

# pypdf + reportlab approach
pip install pypdf reportlab
 
# PyMuPDF approach (faster, simpler API)
pip install pymupdf

PyMuPDF (also known as fitz) is typically faster than the pypdf+reportlab combination because it reads and writes the PDF content stream directly without creating intermediate overlay documents.

Skip numbering for the first N pages

A common requirement: skip the cover page and table of contents, then start numbering at page 1 for the body content.

// TypeScript — skip first 2 pages, start body at page "1"
pages.forEach((page, index) => {
  const skipPages = 2; // cover + table of contents
  if (index < skipPages) return; // no number on these pages
 
  const { width } = page.getSize();
  const displayNumber = index - skipPages + 1; // page 3 of doc = "1"
  const text = `${displayNumber}`;
  const textWidth = font.widthOfTextAtSize(text, fontSize);
 
  page.drawText(text, {
    x: (width - textWidth) / 2,
    y: 20,
    size: fontSize,
    font,
    color: rgb(0.4, 0.4, 0.4),
  });
});

This is sometimes called an "offset" or "starting number" feature. Many online tools support it via a "Start numbering from page N" option.

Number format options

Different documents call for different number formats. Here are the most common:

FormatExampleWhen to use
Plain number3Minimal, slides, short reports
Page X of YPage 3 of 47Long documents, formal reports
Centered dash- 3 -Book chapters, academic papers
Roman numeralsiiiFront matter (intro, TOC)
Bates numberEXHIBIT-000003Legal, litigation, compliance
Section prefix2-3Multi-section technical docs

For Roman numeral conversion in JavaScript:

function toRoman(num: number): string {
  const values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
  const symbols = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"];
  let result = "";
  for (let i = 0; i < values.length; i++) {
    while (num >= values[i]) {
      result += symbols[i];
      num -= values[i];
    }
  }
  return result.toLowerCase(); // "iii" instead of "III"
}

Add page numbers when generating PDFs from HTML

If you generate PDFs from HTML using the PDF4.dev API or a similar HTML-to-PDF service, add page numbers directly in the HTML/CSS using CSS paged media. This is the cleanest approach because the numbering is part of the document design, not a post-processing step.

<!DOCTYPE html>
<html>
<head>
<style>
  @page {
    margin: 20mm 15mm 25mm 15mm;
 
    @bottom-center {
      content: counter(page) " / " counter(pages);
      font-family: Helvetica, sans-serif;
      font-size: 10pt;
      color: #666;
    }
  }
 
  body {
    font-family: Helvetica, sans-serif;
  }
</style>
</head>
<body>
  <h1>My Report</h1>
  <p>Page numbers appear automatically in the footer.</p>
</body>
</html>

The counter(page) and counter(pages) CSS values are standard in the CSS Paged Media spec. Chromium-based PDF renderers (including the one PDF4.dev uses) support them natively.

For more CSS print techniques, see the CSS print styles guide.

Adding page numbers via the PDF4.dev REST API

If you need to generate numbered PDFs at scale, use the PDF4.dev API. Include the CSS @page counter technique above in your HTML template, then call the API with your dynamic data.

const response = await fetch("https://api.pdf4.dev/v1/render", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PDF4_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    html: myHtmlTemplate, // includes @page { @bottom-center { content: counter(page) } }
    data: { title: "Q1 Report", sections: reportData },
  }),
});
 
const pdfBuffer = await response.arrayBuffer();

This approach generates the numbered PDF in a single API call. No post-processing step, no second library to install. See the Node.js PDF generation guide for a complete working example.

When post-processing is the right approach

Use a post-processing library (pdf-lib, PyMuPDF) when:

  • You receive pre-existing PDFs from a third-party (scanned documents, uploaded files)
  • The numbering requirement is an afterthought and you cannot change the original generation pipeline
  • You need to add Bates numbers to a batch of unrelated documents for legal purposes

Use CSS @page counters or in-template numbering when:

  • You control the HTML that generates the PDF
  • The page number style needs to match the document design closely
  • You want a single-step pipeline with no additional dependencies

Batch processing: number multiple PDFs

To number hundreds of PDFs in a folder, loop over the files in Node.js:

import { readdirSync } from "fs";
import path from "path";
 
const inputDir = "./pdfs";
const outputDir = "./pdfs-numbered";
 
const files = readdirSync(inputDir).filter((f) => f.endsWith(".pdf"));
 
for (const file of files) {
  await addPageNumbers(
    path.join(inputDir, file),
    path.join(outputDir, file)
  );
  console.log(`Numbered: ${file}`);
}

For parallel processing across a large batch, use Promise.all with a concurrency limit:

import pLimit from "p-limit"; // npm install p-limit
 
const limit = pLimit(4); // 4 concurrent operations
 
await Promise.all(
  files.map((file) =>
    limit(() =>
      addPageNumbers(
        path.join(inputDir, file),
        path.join(outputDir, file)
      )
    )
  )
);

Common issues

Page numbers appear on the wrong side when printed double-sided. Use alternating positions: bottom-right on odd pages, bottom-left on even pages. In pdf-lib, check index % 2 === 0 to determine left or right alignment.

Page numbers overlap existing content. Increase the bottom page margin before generating the PDF, or lower the Y position of the number stamp (e.g., from y: 20 to y: 10). If working with a pre-existing PDF you cannot regenerate, you may need to set a crop box or add a white rectangle behind the number.

Roman numerals needed for front matter only. Split the PDF by section, apply Roman numerals to the first N pages and Arabic numerals to the rest, then merge the sections back together.

Numbers appear blurry or pixelated. This happens when the PDF is treated as a raster image internally. Ensure you are using vector text operations (pdf-lib drawText, PyMuPDF insert_text) rather than rasterizing the page first.

Tools at a glance

ApproachEffortBest for
PDF4.dev number-pdf toolZeroQuick one-off, any document
CSS @page counterLowNew PDFs generated from HTML
pdf-lib (Node.js)MediumBatch automation, existing PDFs
PyMuPDF (Python)MediumPython workflows, large batches
pdf-lib + reportlabMedium-highPython, complex overlay needs

For most developers generating PDFs from scratch, CSS @page counters are the cleanest solution. For modifying existing PDFs, pdf-lib (Node.js) or PyMuPDF (Python) are the fastest options to add to an existing pipeline.

Once you have page numbers sorted, the next common task is reordering pages or splitting the document into sections.

Free tools mentioned:

Number PdfTry it freeMerge PdfTry it freeReorder PdfTry it freeSplit PdfTry it free

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.