Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use PyMuPDF when you want the shortest reliable workflow: open the PDF, insert the watermark image into each page as an underlay, and save a new file. The complete script below preserves the watermark behind existing text. If you need explicit scaling, rotation, selective pages, or PDF-based composition, use the pypdf method instead.

What “watermark” means in a PDF

PDF libraries distinguish a watermark from a stamp by its layer. A watermark is placed beneath existing page content, so text and graphics remain readable over it. A stamp is placed on top. In pypdf documentation, over=False creates the underlay and over=True creates the overlay. PyMuPDF’s image insertion can likewise place an image at the base of a page.

Your image is positioned in PDF page coordinates. The image should have an intentional opacity and aspect ratio; stretching a logo to fill a page usually produces a distorted result. A transparent PNG is often the easiest source because its alpha channel controls how strongly the page shows through.

Install the Python libraries

PyMuPDF

python -m pip install --upgrade pymupdf

Recent PyMuPDF releases are imported as pymupdf. If your installed release uses the older module name, consult that release’s import guidance rather than mixing APIs from different versions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

pypdf and Pillow

python -m pip install --upgrade pypdf Pillow

The pypdf workflow converts the image to a one-page PDF with Pillow, then merges that page into each source page.

Fastest method: watermark every page with PyMuPDF

This script inserts watermark.png over the entire page rectangle and places it behind the original content.

import pymupdf

input_path = "document.pdf"
watermark_path = "watermark.png"
output_path = "watermarked-document.pdf"

doc = pymupdf.open(input_path)
try:
    for page in doc:
        page.insert_image(
            page.bound(),
            filename=watermark_path,
            overlay=False,
        )
    doc.save(output_path)
finally:
    doc.close()

print(f"Saved {output_path}")

page.bound() returns the page rectangle, so the image is fitted to the page area. overlay=False is the important setting: the image is inserted below existing content. The output is written to a different file, leaving the source unchanged.

Reuse image data for large documents

When the same image is inserted repeatedly, reuse its image data rather than repeatedly reading and embedding the file. This can reduce memory use and repeated image data in the output. A practical pattern is to read the image once and pass the resulting bytes where supported by your PyMuPDF version:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import pymupdf

with open("watermark.png", "rb") as image_file:
    watermark_bytes = image_file.read()

doc = pymupdf.open("document.pdf")
try:
    for page in doc:
        page.insert_image(page.bound(), stream=watermark_bytes, overlay=False)
    doc.save("watermarked-document.pdf")
finally:
    doc.close()

Check the installed version’s API if the stream argument is unavailable. The underlying principle remains the same: embed one reusable image rather than creating a separate copy for every page.

Control placement, size, and page selection

Full-page insertion is convenient, but a logo is usually better placed in a smaller rectangle. PyMuPDF accepts a rectangle instead of the complete page bounds.

import pymupdf

doc = pymupdf.open("document.pdf")
try:
    for page in doc:
        page_rect = page.rect
        width = 140
        height = 60
        margin = 24
        rect = pymupdf.Rect(
            page_rect.x1 - margin - width,
            page_rect.y1 - margin - height,
            page_rect.x1 - margin,
            page_rect.y1 - margin,
        )
        page.insert_image(rect, filename="logo.png", overlay=False)
    doc.save("watermarked-document.pdf")
finally:
    doc.close()

This example puts a 140-by-60-unit image rectangle near the lower-right corner. PDF units are points (72 points per inch), but the visible result also depends on the source image’s pixel dimensions and the PDF viewer’s zoom.

Apply the image only to selected pages

import pymupdf

doc = pymupdf.open("document.pdf")
try:
    for index, page in enumerate(doc):
        if index in {0, 2, 4}:  # zero-based page indexes
            page.insert_image(page.bound(), filename="watermark.png", overlay=False)
    doc.save("selected-pages.pdf")
finally:
    doc.close()

Use zero-based indexes in the loop. For a human page range such as pages 3 through 7, test 3 <= index + 1 <= 7.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

pypdf method: merge an image-based PDF under each page

pypdf does not directly merge a raster image into a page. Convert the image to a one-page PDF with Pillow, then merge that page with over=False.

from io import BytesIO
from PIL import Image
from pypdf import PdfReader, PdfWriter


def image_to_pdf(path):
    image = Image.open(path)
    # RGB avoids issues with image modes that a PDF writer cannot encode.
    if image.mode not in ("RGB", "L"):
        image = image.convert("RGB")
    buffer = BytesIO()
    image.save(buffer, "PDF")
    buffer.seek(0)
    return PdfReader(buffer)

source = PdfReader("document.pdf")
watermark = image_to_pdf("watermark.png").pages[0]
writer = PdfWriter()

for page_number, page in enumerate(source.pages):
    page.merge_transformed_page(watermark, over=False)
    writer.add_page(page)

with open("watermarked-document.pdf", "wb") as output:
    writer.write(output)

The watermark page’s dimensions come from the image-to-PDF conversion. If those dimensions do not match your source pages, use a transformation to scale or translate the watermark.

Scale, move, or rotate the watermark

from pypdf import Transformation

transform = (
    Transformation()
    .scale(sx=0.35, sy=0.35)
    .rotate(15)
    .translate(tx=120, ty=180)
)

for page in source.pages:
    page.merge_transformed_page(watermark, transform, over=False)
    writer.add_page(page)

Transformation order matters. Scale first, then rotate and translate, and inspect the result on pages with different sizes. For a foreground stamp, change over=False to over=True.

Handle rotated pages

PDF pages can carry a rotation attribute separate from their content. If a pypdf watermark appears rotated or displaced, transfer the page rotation into its content before merging:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
for page in source.pages:
    page.transfer_rotation_to_content()
    page.merge_transformed_page(watermark, over=False)
    writer.add_page(page)

Do this only when you need to normalize the page’s rotation; it changes the page content representation.

PyMuPDF or pypdf?

Need Best fit Reason
Fit one image to every page with minimal code PyMuPDF Direct image insertion with page.insert_image and a page rectangle.
Explicit underlay or overlay semantics pypdf over=False and over=True make the layer choice explicit.
Scaling, translation, or rotation of a reusable watermark page pypdf Use merge_transformed_page with Transformation.
Selective page loops and direct raster placement PyMuPDF Insert an image only in pages that meet your condition.
Many pages using the same image Either Reuse image data or a reusable watermark page; neither project’s documentation establishes a universal speed percentage.

Validation checklist after writing the file

  • Open the output in more than one PDF viewer.
  • Check a portrait page, a landscape page, and any rotated page.
  • Confirm text selection and search still work.
  • Zoom in on the watermark edges to detect unwanted stretching.
  • Verify that transparent areas remain transparent and that the watermark is behind text.
  • Compare the output file size with the source; repeated image embedding can increase it.
  • Keep the original PDF until the output has passed your checks.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

The watermark covers the text

With PyMuPDF, set overlay=False. With pypdf, call merge_transformed_page(..., over=False). If you intentionally want a stamp, use the foreground setting instead.

The watermark is upside down or shifted

Inspect page rotation and normalize it with transfer_rotation_to_content() in the pypdf workflow. Also check whether your transform’s translation assumes a page origin or size different from the current page.

The image is distorted

Do not force a non-proportional rectangle. Compute the destination height from the image’s aspect ratio, or prepare a canvas with the desired dimensions before insertion.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Only some pages receive the watermark

Check zero-based indexes and conditions in your loop. A page range written for human numbering must use index + 1.

The output cannot be opened

Make sure the destination is not the same file being read, close the document before reopening it, and confirm that the process has write permission. In the Pillow route, convert unusual image modes such as palette or RGBA to RGB when creating the temporary PDF.

The file becomes unexpectedly large

Reuse the same image bytes in PyMuPDF or the same watermark page in pypdf. Reduce the source image’s pixel dimensions and use an appropriate compression format; a watermark does not need camera-resolution imagery.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server, useful when your PDF workflow starts with web pages that must be captured first. It removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, or another MCP client call screenshot tools directly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a one-call capture, see the ScreenshotNeo documentation:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

The same endpoint works from cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Or Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo includes full-page capture, device and viewport controls, dark mode, CSS selectors, custom JavaScript, PDF output, caching, signed links, asynchronous jobs, bulk capture, and usage reporting on every plan. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Can I watermark an encrypted or password-protected PDF?

You must open it with the appropriate password or permissions first. Neither workflow bypasses encryption.

Does a watermark permanently flatten the PDF?

The inserted image becomes part of the saved page content, but PDF objects may remain extractable. If your requirement is tamper resistance, add a separate signing or document-security process.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I use a JPEG instead of a PNG?

Yes. PNG is preferable when you need transparency; JPEG is suitable for an opaque photographic watermark.

Quick Recap

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.