Skip to content

Instantly share code, notes, and snippets.

@milnak
Created April 8, 2026 05:06
Show Gist options
  • Select an option

  • Save milnak/b596986f4c6b877ff87c787919f481f2 to your computer and use it in GitHub Desktop.

Select an option

Save milnak/b596986f4c6b877ff87c787919f481f2 to your computer and use it in GitHub Desktop.
Adds bookmarks to a PDF file from a CSV list of titles and page numbers.
#!/usr/bin/env python3
"""Add bookmarks to a PDF from a CSV file.
CSV format: <title>,<filename>,<page>
Usage: python pdf-add-csv-bookmarks.py <csv_file> <pdf_file>
python -m venv venv
.\venv\Scripts\pip install pypdf # Windows
source venv/bin/activate && pip install pypdf # macOS/Linux
"""
import argparse
import csv
import sys
from pathlib import Path
from pypdf import PdfReader, PdfWriter
def add_bookmarks(csv_path: Path, pdf_path: Path) -> None:
reader = PdfReader(str(pdf_path))
writer = PdfWriter()
writer.append(reader)
with csv_path.open(newline="", encoding="utf-8") as f:
for lineno, row in enumerate(csv.reader(f), start=1):
if len(row) < 3:
print(f"Warning: skipping malformed line {lineno}: {row}", file=sys.stderr)
continue
title = row[0].strip()
page_str = row[2].strip()
try:
page_num = int(page_str)
except ValueError:
print(f"Warning: invalid page number on line {lineno}: {page_str!r}", file=sys.stderr)
continue
if page_num < 1 or page_num > len(reader.pages):
print(f"Warning: page {page_num} out of range on line {lineno}", file=sys.stderr)
continue
writer.add_outline_item(title, page_num - 1) # pypdf uses 0-based page index
output_path = pdf_path.with_stem(pdf_path.stem + "_bookmarked")
with output_path.open("wb") as out:
writer.write(out)
print(f"Saved: {output_path}")
def main() -> None:
parser = argparse.ArgumentParser(description="Add bookmarks to a PDF from a CSV file.")
parser.add_argument("csv_file", help="CSV file with columns: title, filename, page")
parser.add_argument("pdf_file", help="Input PDF file")
args = parser.parse_args()
csv_path = Path(args.csv_file)
pdf_path = Path(args.pdf_file)
if not csv_path.exists():
sys.exit(f"Error: CSV file not found: {csv_path}")
if not pdf_path.exists():
sys.exit(f"Error: PDF file not found: {pdf_path}")
add_bookmarks(csv_path, pdf_path)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment