Use PyMuPDF when you need to stamp the same logo, watermark, signature, badge, or product image onto every page of a PDF without touching each page by hand. It is fast, simple to script, and works well for both one-off documents and bulk processing jobs.

TLDR: Install PyMuPDF, open the PDF, loop through its pages, and insert the same image into a fixed rectangle on each page. For example, a legal assistant can add a “Confidential” PNG watermark to a 120-page contract in under 10 seconds instead of spending 20 minutes in a PDF editor. If your team processes 300 PDFs a month, even saving 3 minutes per file adds up to 15 hours back.

Adding the same image to every PDF page sounds like a tiny task until you actually have to do it. A company logo in the header. A paid stamp. A QR code. A scanned signature. A watermark that says Draft. Doing this manually is dull, error-prone, and oddly easy to mess up on page 37.

Python is a better fit. You write the placement rules once, run the script, and let the computer repeat the boring part perfectly.

Why PyMuPDF is a good choice

There are several Python libraries for PDF work, including pypdf, ReportLab, and PyMuPDF. For this task, PyMuPDF is usually the most direct option because it can open an existing PDF and insert an image onto each page with just a few lines of code.

It also gives you precise control over:

  • Position: place the image in a header, footer, corner, or center.
  • Size: scale the image to exact dimensions.
  • Layering: put the image above or below existing PDF content.
  • Page count: apply it to every page automatically.

Install it with:

pip install pymupdf

You will also need an image file, such as logo.png, watermark.png, or stamp.jpg. PNG is a strong choice if you need transparency.

Basic script: add one image to every page

Here is a complete example. It opens an existing PDF, inserts an image in the top-right corner of every page, and saves a new PDF.

import fitz  # PyMuPDF

input_pdf = "input.pdf"
output_pdf = "output_with_image.pdf"
image_file = "logo.png"

doc = fitz.open(input_pdf)

for page in doc:
    page_width = page.rect.width

    image_width = 120
    image_height = 60

    margin = 36
    x0 = page_width - image_width - margin
    y0 = margin
    x1 = x0 + image_width
    y1 = y0 + image_height

    rect = fitz.Rect(x0, y0, x1, y1)

    page.insert_image(rect, filename=image_file)

doc.save(output_pdf)
doc.close()

This places the image 36 points from the top and right edges. PDF measurements use points, where 72 points equal one inch. So a margin of 36 points is half an inch.

The placement rectangle controls the final image size. If the source image is larger, PyMuPDF scales it to fit. If it is smaller, PyMuPDF scales it up. That can make tiny images look blurry, so start with a high-quality source file.

Understanding the placement rectangle

The most common source of annoyance is positioning. It drives me crazy that PDF coordinates feel simple until the image appears in the wrong corner. The fix is to think in four values:

  • x0: left edge of the image box
  • y0: top edge of the image box
  • x1: right edge of the image box
  • y1: bottom edge of the image box

For a top-left logo, use a small x0 and y0:

rect = fitz.Rect(36, 36, 156, 96)

For a footer image near the bottom center, calculate the position from the page width and height:

image_width = 180
image_height = 50

x0 = (page.rect.width - image_width) / 2
y0 = page.rect.height - image_height - 36
x1 = x0 + image_width
y1 = y0 + image_height

rect = fitz.Rect(x0, y0, x1, y1)

This is useful for page numbers, certification bars, approval stamps, or small brand strips at the bottom of each page.

Adding a watermark across the page

If you want a watermark, create a transparent PNG first. A pale gray “Draft” or “Confidential” image works well. Then place it in the middle of each page.

import fitz

doc = fitz.open("report.pdf")
image_file = "confidential.png"

for page in doc:
    page_width = page.rect.width
    page_height = page.rect.height

    image_width = 350
    image_height = 140

    x0 = (page_width - image_width) / 2
    y0 = (page_height - image_height) / 2
    x1 = x0 + image_width
    y1 = y0 + image_height

    rect = fitz.Rect(x0, y0, x1, y1)

    page.insert_image(rect, filename=image_file, overlay=True)

doc.save("report_watermarked.pdf")
doc.close()

The overlay=True setting places the image on top of the existing page content. Use overlay=False if you want it behind the text. The catch is that some PDFs have solid white backgrounds, so a behind-the-page watermark may disappear. If that happens, use a transparent, low-opacity PNG and place it on top.

Processing many PDFs at once

Once the single-file version works, batch processing is easy. The following script adds the same image to every PDF in a folder and writes the edited files to a separate output folder.

import fitz
from pathlib import Path

input_folder = Path("pdfs")
output_folder = Path("finished")
output_folder.mkdir(exist_ok=True)

image_file = "logo.png"

for pdf_path in input_folder.glob("*.pdf"):
    doc = fitz.open(pdf_path)

    for page in doc:
        rect = fitz.Rect(36, 36, 156, 96)
        page.insert_image(rect, filename=image_file)

    output_path = output_folder / f"{pdf_path.stem}_stamped.pdf"
    doc.save(output_path)
    doc.close()

    print(f"Saved {output_path}")

This is where Python really pays off. If each manual edit takes one minute and you have 500 documents, that is more than 8 hours of clicking. A batch script can often finish the same work during a coffee break.

Common problems and quick fixes

  • The image looks blurry: use a higher-resolution source image and scale down, not up.
  • The image covers text: move it to a margin area or use a transparent PNG.
  • The image is too large: reduce the rectangle width and height in the script.
  • The file size grows too much: compress the image before inserting it.
  • The image is on the wrong page area: print page.rect.width and page.rect.height to inspect page size.

One practical tip: before running a batch job, test the script on a two-page sample PDF. Open the output, zoom in, and confirm the position. Expect to waste time on tiny placement tweaks if you skip this step.

When to use this approach

This technique is ideal for recurring document tasks. Finance teams can stamp invoice PDFs. Schools can add department logos to handouts. Agencies can brand client reports. Legal teams can mark drafts. Sellers can place QR codes on digital catalogs.

It is less ideal if every page needs a different image or custom placement. Python can still handle that, but you will need rules that map images to pages. For example, page 1 gets a cover badge, pages 2 through 8 get a watermark, and the final page gets a signature block.

A clean reusable function

For regular use, wrap the logic in a function:

import fitz

def add_image_to_pdf(input_pdf, output_pdf, image_file, rect):
    doc = fitz.open(input_pdf)

    for page in doc:
        page.insert_image(rect, filename=image_file)

    doc.save(output_pdf)
    doc.close()

stamp_area = fitz.Rect(36, 36, 156, 96)

add_image_to_pdf(
    "input.pdf",
    "output.pdf",
    "logo.png",
    stamp_area
)

This keeps your script tidy. You can reuse it in internal tools, scheduled jobs, or small desktop utilities. You can also extend it with options for placement, opacity through prepared transparent images, batch folders, or different stamps for different document types.

The simple version is enough for most needs: open the PDF, loop through pages, insert the image, and save a new file. That small script can remove hours of repetitive editing from your week, and it does it without asking you to drag the same logo onto page after page like it is still 2009.