Client-side

PAGE NUMBERS

Quickly number your PDF document pages. Configure positioning, index formatting, and range offsets locally.

🔢
Drag & drop a PDF file here, or click to browse
Supports single .pdf file

About Page Numbers Tool

Organize and index your PDF documents by inserting custom page numbers at the header or footer of your files. Supports custom offsets, alignment configurations (center or right), and multiple numbering formats (e.g. 'Page X of Y').

Page stamping is processed entirely inside your local browser tab. Ideal for legal documents, eBooks, academic papers, and multi-page corporate reports.

Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

let currentPdfBytes = null;
    let totalPages = 0;
    let fileNameStr = '';

    const uploadZone = document.getElementById('uploadZone');
    const fileInput = document.getElementById('fileInput');
    const configPanel = document.getElementById('configPanel');
    const fileNameEl = document.getElementById('fileName');
    const pageCountEl = document.getElementById('pageCount');
    const posSelect = document.getElementById('posSelect');
    const formatSelect = document.getElementById('formatSelect');
    const startFromInput = document.getElementById('startFrom');
    const addBtn = document.getElementById('addBtn');
    const loadingContainer = document.getElementById('loadingContainer');
    const resultContainer = document.getElementById('resultContainer');
    const resultInfo = document.getElementById('resultInfo');
    const downloadBtn = document.getElementById('downloadBtn');

    // Drag and drop event listeners
    ['dragenter', 'dragover'].forEach(eventName => {
      uploadZone.addEventListener(eventName, e => {
        e.preventDefault();
        uploadZone.classList.add('dragover');
      }, false);
    });

    ['dragleave', 'drop'].forEach(eventName => {
      uploadZone.addEventListener(eventName, e => {
        e.preventDefault();
        uploadZone.classList.remove('dragover');
      }, false);
    });

    uploadZone.addEventListener('drop', e => {
      const dt = e.dataTransfer;
      const files = dt.files;
      if (files.length > 0) handleFile(files[0]);
    });

    fileInput.addEventListener('change', e => {
      if (e.target.files.length > 0) handleFile(e.target.files[0]);
    });

    async function handleFile(file) {
      if (file.type !== 'application/pdf') return;
      fileNameStr = file.name;

      try {
        currentPdfBytes = await file.arrayBuffer();
        const { PDFDocument } = PDFLib;
        const pdf = await PDFDocument.load(currentPdfBytes);
        totalPages = pdf.getPageCount();

        fileNameEl.textContent = file.name;
        pageCountEl.textContent = `${totalPages} page${totalPages > 1 ? 's' : ''}`;
        
        configPanel.classList.remove('hidden');
        resultContainer.classList.add('hidden');
      } catch (err) {
        console.error(err);
        alert('Could not read PDF file. Make sure it is not encrypted.');
      }
    }

    function formatBytes(bytes, decimals = 2) {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const dm = decimals < 0 ? 0 : decimals;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    }

    addBtn.addEventListener('click', async () => {
      if (!currentPdfBytes) return;

      configPanel.classList.add('hidden');
      loadingContainer.classList.remove('hidden');
      resultContainer.classList.add('hidden');

      try {
        const { PDFDocument, rgb, StandardFonts } = PDFLib;
        const pdfDoc = await PDFDocument.load(currentPdfBytes);
        const font = await pdfDoc.embedFont(StandardFonts.Helvetica);

        const position = posSelect.value;
        const pattern = formatSelect.value;
        const startFrom = parseInt(startFromInput.value) || 1;

        const pages = pdfDoc.getPages();

        for (let i = 0; i < pages.length; i++) {
          const page = pages[i];
          const pageNum = i + 1;

          if (pageNum < startFrom) continue; // Skip title/start pages if selected

          const { width, height } = page.getSize();
          const nVal = pageNum - startFrom + 1;
          const totalVal = pages.length - startFrom + 1;

          let textStr = '';
          if (pattern === 'page-of') textStr = `Page ${nVal} of ${totalVal}`;
          else if (pattern === 'n-total') textStr = `${nVal} / ${totalVal}`;
          else if (pattern === 'n-only') textStr = `${nVal}`;

          const fontSize = 10;
          const textWidth = font.widthOfTextAtSize(textStr, fontSize);

          let x = width / 2 - textWidth / 2; // Footer center
          let y = 30; // Footer baseline

          if (position === 'footer-right') {
            x = width - textWidth - 30;
          } else if (position === 'header-center') {
            x = width / 2 - textWidth / 2;
            y = height - 40;
          } else if (position === 'header-right') {
            x = width - textWidth - 30;
            y = height - 40;
          }

          page.drawText(textStr, {
            x: x,
            y: y,
            size: fontSize,
            font: font,
            color: rgb(0.4, 0.4, 0.4)
          });
        }

        const numberedBytes = await pdfDoc.save();
        const blob = new Blob([numberedBytes], { type: 'application/pdf' });
        const blobUrl = URL.createObjectURL(blob);

        downloadBtn.href = blobUrl;
        const nameWithoutExt = fileNameStr.replace(/\.[^/.]+$/, "");
        downloadBtn.download = `${nameWithoutExt}_numbered.pdf`;
        resultInfo.textContent = `Numbered ${pages.length - startFrom + 1} page(s). File size: ${formatBytes(blob.size)}.`;

        loadingContainer.classList.add('hidden');
        resultContainer.classList.remove('hidden');
      } catch (err) {
        console.error(err);
        alert('An error occurred while inserting page numbers.');
        loadingContainer.classList.add('hidden');
        configPanel.classList.remove('hidden');
      }
    });