Adding Watermarks to PDFs with PyMuPDF: A Complete Guide

Jamie Lemon·June 12, 2025

PyMuPDFWatermarks
Adding Watermarks to PDFs with PyMuPDF

PDF watermarking is an essential technique for protecting intellectual property, adding branding to documents, and maintaining document security. Whether you're marking confidential business documents, adding your company logo to reports, or protecting creative work, watermarks provide a professional and effective solution.

PyMuPDF stands out as one of the most powerful and efficient Python libraries for PDF manipulation. Unlike other PDF libraries that can be slow or limited in functionality, PyMuPDF offers fast processing, extensive customization options, and excellent support for both text and image watermarks.

In this comprehensive guide, you'll learn everything from basic text watermarking to advanced batch processing techniques, complete with practical code examples you can use immediately in your projects.


Setting Up Your Environment

Getting started with PyMuPDF is straightforward. Install the library using pip:

pip install pymupdf

PyMuPDF has minimal dependencies and works across Windows, macOS, and Linux. For image watermarking, you might also want to install Pillow for additional image processing capabilities:

pip install pillow

Here are the essential imports you'll need for most watermarking tasks:

import pymupdf 
import os
from datetime import datetime


Understanding PDF Watermarks

Before diving into code, it's important to understand the two main types of watermarks you can add to PDFs:

Text Watermarks are perfect for adding copyright notices, confidentiality labels, or dynamic information like timestamps. They're lightweight, customizable, and don't significantly increase file size.

Image Watermarks work well for logos, signatures, or any graphical element that needs to appear on your documents. They provide more visual impact but require careful consideration of file size and positioning.

PyMuPDF uses a coordinate system where (0, 0) represents the top-left corner of each page. Understanding this coordinate system is crucial for precise watermark placement.

Basic Text Watermarking

Let's start with a simple example that adds a "CONFIDENTIAL" watermark to every page of a PDF:

import pymupdf

def add_text_watermark(input_pdf, output_pdf, watermark_text):
    # Open the PDF document
    doc = pymupdf.open(input_pdf)

    for page_num in range(doc.page_count):
        page = doc[page_num]

        # Get page dimensions
        page_rect = page.rect

        # Calculate center position
        x = page_rect.width / 2
        y = page_rect.height / 2

        # Insert text watermark
        page.insert_text(
            (x, y),  # Position
            watermark_text,  # Text
            fontsize=50,
            color=(0.7, 0.7, 0.7),  # Light gray
            rotate=90  # Vertical orientation
        )

    # Save the watermarked PDF
    doc.save(output_pdf)
    doc.close()

# Usage
add_text_watermark("test.pdf", "output.pdf", "CONFIDENTIAL")

This basic function demonstrates the core concepts: opening a document, iterating through pages, calculating positions, and inserting text with custom formatting.

Advanced Text Watermarking

For more sophisticated text watermarks, you can customize fonts, create multi-line watermarks, and add dynamic content:

import pymupdf
import os
from datetime import datetime

def advanced_text_watermark(input_pdf, output_pdf):
    doc = pymupdf.open(input_pdf)
    
    # Get current timestamp for dynamic watermark
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
    
    for page_num in range(doc.page_count):
        page = doc[page_num]
        page_rect = page.rect
        
        # Main watermark text
        main_text = "CONFIDENTIAL"
        page.insert_text(
            (page_rect.width - 100, page_rect.height - 10),
            main_text,
            fontsize=100,
            color=(0.8, 0.2, 0.2),  # Red color
            rotate=90,
            stroke_opacity = 0.5,
            render_mode=1, # outline the font
            fontname="Courier-Bold"  # Bold font
        )
        
        # Timestamp watermark in corner
        page.insert_text(
            (20, 30),  # top-left corner
            f"Generated: {timestamp}",
            fontsize=10,
            color=(0.5, 0.5, 0.5),
            rotate=0
        )
        
        # Page number watermark
        page.insert_text(
            (page_rect.width - 100, 30), # top-right corner
            f"Page {page_num + 1} of {doc.page_count}",
            fontsize=10,
            color=(0.5, 0.5, 0.5),
            rotate=0
        )
    
    doc.save(output_pdf)
    doc.close()

advanced_text_watermark("test.pdf", "output.pdf")

This advanced example shows how to add multiple watermarks with different positioning, colors, and content types on each page.

Image Watermarking

Adding image watermarks requires a slightly different approach. Here's how to add a logo or image to your PDFs:

import pymupdf

def add_image_watermark(input_pdf, output_pdf, watermark_image):
    doc = pymupdf.open(input_pdf)
    
    # create a pixmap from the image
    pixmap = pymupdf.Pixmap(watermark_image)
    
    for page_num in range(doc.page_count):
        page = doc[page_num]
        page_rect = page.rect
        
        # Calculate scaling to fit image appropriately
        scale_x = page_rect.width * 0.3 / pixmap.width  # 30% of page width
        scale_y = page_rect.height * 0.3 / pixmap.height  # 30% of page height
        scale = min(scale_x, scale_y)  # Maintain aspect ratio
        
        # Calculate position (center of page)
        img_width = pixmap.width * scale
        img_height = pixmap.height * scale
        x = (page_rect.width - img_width) / 2
        y = (page_rect.height - img_height) / 2
        
        # Define the rectangle where image will be placed
        target_rect = pymupdf.Rect(x, y, x + img_width, y + img_height)
        
        # Insert the pixmap image at the back of the page
        page.insert_image(target_rect, pixmap=pixmap, overlay=False)

    doc.save(output_pdf)
    doc.close()

# Usage
add_image_watermark("test.pdf", "logo_watermarked.pdf", "logo.png")
Note

If you want to add transparency to a watermark image it is recommended to use the Pillow library to prepare your image, or build in the transparency into your PNG graphic. Additionally when you add the watermark with insert_image then the overlay=False parameter definition ensures that the image will be placed behind all other page content.

Watermark Positioning and Layout

Precise positioning is crucial for professional-looking watermarks. Here are common positioning strategies:

import pymupdf

def position_watermarks(input_pdf, output_pdf):
    doc = pymupdf.open(input_pdf)
    
    positions = {
        'center': lambda rect: (rect.width/2, rect.height/2),
        'bottom_left': lambda rect: (50, rect.height - 50),
        'bottom_right': lambda rect: (rect.width - 150, rect.height - 50),
        'top_left': lambda rect: (50, 50),
        'top_right': lambda rect: (rect.width - 150, 50)
    }
    
    for page_num in range(doc.page_count):
        page = doc[page_num]
        page_rect = page.rect
        
        # Add large watermark at center
        center_pos = positions['center'](page_rect)
        page.insert_text(
            center_pos,
            "DRAFT",
            fontsize=60,
            color=(0.9, 0.0, 0.0),
            rotate=0
        )
        
        # Add small copyright notice in bottom right
        br_pos = positions['bottom_right'](page_rect)
        page.insert_text(
            br_pos,
            "© 2025 Your Company",
            fontsize=12,
            color=(0.4, 0.4, 0.4),
            rotate=0
        )
    
    doc.save(output_pdf)
    doc.close()

position_watermarks("test.pdf", "output.pdf")

Styling and Visual Effects

Professional watermarks require careful attention to visual styling. Here's how to create polished, subtle watermarks:

import pymupdf

def styled_watermark(input_pdf, output_pdf, watermark_text):
    doc = pymupdf.open(input_pdf)
    
    for page_num in range(doc.page_count):
        page = doc[page_num]
        page_rect = page.rect
        
        # Semi-transparent background rectangle
        bg_rect = pymupdf.Rect(
            page_rect.width/2 - 120,
            page_rect.height/2 - 30,
            page_rect.width/2 + 120,
            page_rect.height/2 + 30
        )
        
        # Add background with border
        page.draw_rect(bg_rect, color=(0.9, 0.9, 0.9), fill=(0.95, 0.95, 0.95), width=1)
        
        # Add main watermark text
        page.insert_text(
            (page_rect.width/2 - 80, page_rect.height/2 + 5),
            watermark_text,
            fontsize=16,
            color=(0.6, 0.6, 0.6)
        )
        
        # Add decorative elements
        page.draw_line(
            pymupdf.Point(page_rect.width/2 - 100, page_rect.height/2 - 15),
            pymupdf.Point(page_rect.width/2 + 100, page_rect.height/2 - 15),
            color=(0.7, 0.7, 0.7),
            width=0.5
        )
        
        page.draw_line(
            pymupdf.Point(page_rect.width/2 - 100, page_rect.height/2 + 15),
            pymupdf.Point(page_rect.width/2 + 100, page_rect.height/2 + 15),
            color=(0.7, 0.7, 0.7),
            width=0.5
        )
    
    doc.save(output_pdf)
    doc.close()

styled_watermark("test.pdf", "output.pdf", "SAMPLE DOCUMENT")

Rotated Text & Watermarks

Many typical watermarks like to draw large text diagonally across all document pages like so:

PDF with rotated text watermark on every page

But how do we apply text rotation with PyMuPDF?

Essentially we have to morph the text with a supplied matrix and use some maths follows:

import pymupdf
import math

def add_rotated_text_watermark(input_pdf, output_pdf, watermark_text):
    # Open the PDF document
    doc = pymupdf.open(input_pdf)

    for page_num in range(doc.page_count):
        page = doc[page_num]

        # choose desired font
        font = pymupdf.Font("tiro")
        page.insert_font(fontname="myfont", fontbuffer=font.buffer)
        font_size = 100

        # choose 2 points to define a line along which to insert text
        p1 = pymupdf.Point(100, 750)
        p2 = pymupdf.Point(500, 100)
        # compute angle of line
        cos, sin = (p2 - p1).unit
        theta = math.degrees(math.atan2(sin, cos))
        # define matrix to rotate text
        mat = pymupdf.Matrix(-theta)
        # we want to insert this text along the line
        text = watermark_text #f"This text inserted at {round(-theta,1)}°"
        """
        Optional: Shrink / stretch text to fit along the line
        ---------------------------------------------------------------------
        """
        # length of line
        line_len = abs(p2 - p1)
        text_len = font.text_length(text, fontsize=font_size)
        # scale factor
        scale = line_len / text_len
        # scale matrix
        scale_mat = pymupdf.Matrix(scale, scale)
        # mat *= scale_mat  # (un-)comment to see its effect
        """
        ---------------------------------------------------------------------
        """
        page.insert_text(
            p1,
            text,
            fontsize=font_size,
            fontname="myfont",
            fill_opacity=0.3,
            stroke_opacity=0.3,
            color=(1, 0, 0),
            fill=(1, 1, 1),
            border_width=0.02,
            render_mode=2,
            morph=(p1, mat),
        )
        #page.draw_line(p1, p2, color=(1, 0, 0))

    # Save the watermarked PDF
    doc.save(output_pdf)
    doc.close()

# Usage
add_rotated_text_watermark("sample.pdf", "output.pdf", "CONFIDENTIAL")

Batch Processing Multiple PDFs

When you need to watermark multiple files, batch processing saves time and ensures consistency:

import pymupdf
import os

def batch_watermark_directory(input_dir, output_dir, watermark_text):
    # Create output directory if it doesn't exist
    os.makedirs(output_dir, exist_ok=True)
    
    # Process all PDF files in the input directory
    pdf_files = [f for f in os.listdir(input_dir) if f.lower().endswith('.pdf')]
    
    for i, filename in enumerate(pdf_files, 1):
        input_path = os.path.join(input_dir, filename)
        output_filename = f"watermarked_{filename}"
        output_path = os.path.join(output_dir, output_filename)
        
        try:
            print(f"Processing {i}/{len(pdf_files)}: {filename}")
            
            doc = pymupdf.open(input_path)
            
            for page_num in range(doc.page_count):
                page = doc[page_num]
                page_rect = page.rect
                
                # Add watermark
                page.insert_text(
                    (page_rect.width/2, page_rect.height/2),
                    watermark_text,
                    fontsize=40,
                    color=(0.8, 0.8, 0.8)
                )
            
            doc.save(output_path)
            doc.close()
            
            print(f"✓ Successfully processed: {filename}")
            
        except Exception as e:
            print(f"✗ Error processing {filename}: {str(e)}")
            continue

# Usage
batch_watermark_directory("./input_pdfs", "./watermarked_pdfs", "CONFIDENTIAL")

Error Handling and Edge Cases

Robust error handling ensures your watermarking process works reliably:

import pymupdf
import os

def robust_watermarking(input_pdf, output_pdf, watermark_text):
    try:
        # Check if input file exists
        if not os.path.exists(input_pdf):
            raise FileNotFoundError(f"Input PDF not found: {input_pdf}")
        
        # Open document with error handling
        doc = pymupdf.open(input_pdf)
        
        # Check if document is encrypted
        if doc.needs_pass:
            print(f"Warning: {input_pdf} is password protected. Skipping...")
            doc.close()
            return False
        
        # Check if document has pages
        if doc.page_count == 0:
            print(f"Warning: {input_pdf} has no pages. Skipping...")
            doc.close()
            return False
        
        for page_num in range(doc.page_count):
            try:
                page = doc[page_num]
                page_rect = page.rect
                
                # Handle pages with zero dimensions
                if page_rect.width <= 0 or page_rect.height <= 0:
                    print(f"Warning: Page {page_num + 1} has invalid dimensions. Skipping...")
                    continue
                
                # Add watermark
                page.insert_text(
                    (page_rect.width / 2, page_rect.height / 2),
                    watermark_text,
                    fontsize=min(50, page_rect.width / 10),  # Adaptive font size
                    color=(0.7, 0.7, 0.7),
                    rotate=45
                )
                
            except Exception as page_error:
                print(f"Error processing page {page_num + 1}: {str(page_error)}")
                continue
        
        # Save with error handling
        doc.save(output_pdf)
        doc.close()
        
        print(f"✓ Successfully watermarked: {input_pdf} -> {output_pdf}")
        return True
        
    except Exception as e:
        print(f"✗ Error watermarking {input_pdf}: {str(e)}")
        return False


robust_watermarking("test.pdf", "output.pdf", "CONFIDENTIAL")

Practical Examples and Use Cases

Here are some real-world applications with complete code examples:

Corporate Document Branding

import pymupdf
import os

def corporate_branding_watermark(input_pdf, output_pdf, company_name, logo_path=None):
    doc = pymupdf.open(input_pdf)
    
    for page_num in range(doc.page_count):
        page = doc[page_num]
        page_rect = page.rect
        
        # Company name in header
        page.insert_text(
            (20, 20),
            company_name,
            fontsize=12,
            color=(0.3, 0.3, 0.7),
            fontname="helvetica-bold"
        )
        
        # Confidentiality notice in footer
        page.insert_text(
            (20, page_rect.height - 20),
            "This document contains confidential and proprietary information.",
            fontsize=8,
            color=(0.5, 0.5, 0.5)
        )
        
        # Optional logo watermark
        if logo_path and os.path.exists(logo_path):
            try:
                # create a pixmap from the image
                pixmap = pymupdf.Pixmap(logo_path)

                logo_rect = pymupdf.Rect(
                    page_rect.width - 60,
                    10,
                    page_rect.width - 10,
                    60
                )
                page.insert_image(logo_rect, pixmap=pixmap, overlay=False)

            except:
                pass  # Continue without logo if there's an error
    
    doc.save(output_pdf)
    doc.close()

corporate_branding_watermark("test.pdf", "output.pdf", "Artifex Software Inc.", "logo.png")

Best Practices and Tips

When implementing PDF watermarking, follow these best practices for optimal results:

  • Choose Appropriate Opacity: Watermarks should be visible but not interfere with document readability. Start with 20-30% opacity for image watermarks and light gray colors for text.
  • Consider Document Layout: Position watermarks to avoid covering important content. Test with various document types to ensure consistent placement.
  • File Naming Conventions: Use clear, descriptive names for watermarked files. Consider adding timestamps or version numbers.
  • Version Control: Keep original files separate from watermarked versions. Consider using a systematic folder structure.
  • Testing: Always test watermarking on sample documents before processing important files in batch.
  • Performance: For large batches, consider processing files in smaller groups and implementing progress tracking.
  • Security: Be mindful that watermarks can be removed by determined individuals. For high-security applications, consider additional protection methods.

Conclusion

PyMuPDF provides a powerful and flexible foundation for PDF watermarking that scales from simple text additions to complex, multi-element branding systems. The library's speed and extensive customization options make it ideal for both one-off watermarking tasks and large-scale batch processing.

The techniques covered in this guide give you the tools to implement professional watermarking solutions for various use cases, from corporate document protection to academic paper drafts. The combination of text and image watermarks, along with proper error handling and optimization, ensures your watermarking system will be both reliable and efficient.

Remember that effective watermarking balances visibility with usability. The goal is to protect and brand your documents while maintaining their professional appearance and readability.

For more advanced PDF manipulation techniques beyond watermarking, explore PyMuPDF's extensive documentation. The library offers capabilities for text extraction, form handling, annotation management, and much more, making it a comprehensive solution for all your PDF processing needs.

Start with the basic examples provided here, then gradually incorporate more advanced features as your requirements grow. With PyMuPDF's robust API and the patterns demonstrated in this guide, you'll be able to create sophisticated PDF watermarking solutions tailored to your specific needs.