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');
}
});