Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

pdfluent

Enterprise PDF SDK for Python — built on a pure-Rust stack, zero system dependencies.

Render pages, extract text, fill forms, annotate, redact, encrypt, merge, and validate PDF/A — all from a single pip install.

Installation

pip install pdfluent

# Optional extras
pip install pdfluent[pillow]   # PIL Image support
pip install pdfluent[numpy]    # NumPy array support

Requires Python ≥ 3.8. Pre-built wheels for Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (x86_64).

Quick Start

from pdfluent import Document

# Open, inspect, render
with Document("invoice.pdf") as doc:
    print(f"{doc.page_count} pages — {doc.metadata.title}")

    img = doc[0].render(dpi=150)
    img.save("page_0.png")          # requires Pillow

# Extract text
doc = Document("report.pdf")
for page in doc:
    print(page.extract_text())

# Fill a form field and save
doc = Document("form.pdf")
doc.set_form_field("Name", "Jane Doe")
# Multi-select list box: select several options at once
doc.set_multi_select("Languages", ["EN", "NL"])
doc.save("form_filled.pdf")

# Search-and-redact
doc = Document("contract.pdf")
report = doc.redact_text("Confidential")
print(f"Redacted {report.areas_redacted} areas on {report.pages_affected} pages")
doc.save("contract_redacted.pdf")

# PDF/A validation
from pdfluent import validate_pdfa

report = validate_pdfa("archive.pdf")
if report.is_compliant:
    print(f"✓ {report.pdfa_level} compliant")
else:
    for issue in report.issues:
        print(f"[{issue.severity}] {issue.rule}: {issue.message}")

# Merge PDFs
from pdfluent import merge_pdfs
merge_pdfs(["a.pdf", "b.pdf", "c.pdf"], "merged.pdf")

# Encrypt / decrypt
doc = Document("sensitive.pdf")
doc.encrypt("sensitive_enc.pdf", password="s3cr3t")

from pdfluent import decrypt_pdf
decrypt_pdf("sensitive_enc.pdf", "sensitive_dec.pdf", password="s3cr3t")

Features

Feature Description
Render Pages to RGBA pixels, PIL Images, or NumPy arrays at any DPI
Text extraction Plain text or structured TextBlock/TextSpan with position
Text search Find pages containing a query string
Forms (AcroForm) Read and fill text, checkbox, radio, combo/list fields; multi-select list boxes (set_multi_select); hierarchical names; appearance-stream regeneration
Annotations Read existing annotations; add highlights and free-text notes
Redaction Search-and-redact: black-box all occurrences of a string
Encryption AES-256 (PDF 2.0) encrypt/decrypt with user + owner passwords
Merge / split Merge multiple PDFs; split into individual pages (via page slicing)
PDF/A validation Validate against PDF/A-1B, 2B, 3B with issue-level reporting
Metadata Read title, author, subject, keywords, creator, producer
Bookmarks Traverse the document outline tree
Thumbnails Fast downscaled preview images

API Overview

Document(source, password=None)

Opens a PDF from a file path (str) or raw bytes.

doc = Document("file.pdf")             # from path
doc = Document(open("file.pdf","rb").read())  # from bytes
doc = Document("encrypted.pdf", password="pw")

Properties: page_count, metadata, bookmarks
Methods: render_all(dpi), search(query), extract_text(page_num), save(path), get_form_fields(), set_form_field(name, value), get_annotations(page), add_annotation(page, type, rect, content), redact_text(term, page=None), encrypt(path, password), decrypt(path, password)
Protocols: len(doc), doc[0], for page in doc, with Document(...) as doc

AcroForm fill behaviour

set_form_field(name, value) supports text, checkbox, radio, and choice (combo/list) fields. Hierarchical names ("parent.child") are resolved through /Kids recursion. Every call keeps /V, /AS, and /AP consistent so the fill is visible in all PDF viewers without needing /NeedAppearances processing.

doc.set_form_field("Name", "Jane Doe")          # text field
doc.set_form_field("Agree", "On")               # checkbox — pass on-state name
doc.set_form_field("Country", "NL")             # radio group — export value
doc.set_form_field("Category", "Urgent")        # combo / list — option value
doc.save("form_filled.pdf")

Read-only fields raise PdfluentError.

Page

Properties: index, width, height, rotation, geometry
Methods: render(dpi, width, height, background), thumbnail(max_dimension), extract_text(), extract_text_blocks()

RenderedImage

Properties: width, height, pixels (raw RGBA bytes)
Methods: to_pil(), to_numpy(), save(path)

TextSpan

Structured text with position data.

Properties: text, x, y, font_size
G1 font-metadata (Optional): font_name, is_bold, is_italic, color

G1 fields return None in the current release. They are typed as Optional so downstream code handles the None case correctly today and will automatically receive data once the G1 extraction milestone lands.

for block in page.extract_text_blocks():
    for span in block.spans:
        if span.font_name is not None:
            print(f"{span.font_name} {'bold' if span.is_bold else ''}")
        print(f"  '{span.text}' @ ({span.x:.1f}, {span.y:.1f})")

Module-level functions

Function Description
open_pdf(path, password=None) Alias for Document(path)
merge_pdfs(paths, output) Merge a list of PDFs
validate_pdfa(path)ComplianceReport Run PDF/A validation
decrypt_pdf(input, output, password) Decrypt to a new file

Exception Hierarchy

Every pdfluent-specific error derives from PdfluentError, so a single except PdfluentError: clause catches all library errors:

from pdfluent import PdfluentError, PdfluentParseError, PdfluentEncryptedError

try:
    with Document("broken.pdf") as doc:
        doc.render_all()
except PdfluentParseError as exc:
    print(f"Not a valid PDF: {exc}")
except PdfluentEncryptedError:
    print("PDF is password-protected")
except PdfluentError as exc:
    print(f"PDF error: {exc}")

Full hierarchy:

PdfluentError                 — base; catch all pdfluent errors
├── PdfluentParseError        — corrupt / non-PDF bytes
├── PdfluentValidationError   — schema / compliance failures
├── PdfluentRenderError       — rendering and XFA flatten failures
├── PdfluentEncryptedError    — operation blocked by encryption
├── PdfluentPageRangeError    — page index out of range
├── PdfluentIoError           — file-system I/O errors
├── PdfluentLicenseError      — invalid / expired license
├── PdfluentGeometryError     — invalid page geometry
└── PdfluentLimitError        — processing-limit exceeded

Typing Support

pdfluent ships with hand-written .pyi stub files for IDE completion and mypy --strict compatibility:

  • pdfluent/__init__.pyi — full public API stubs
  • pdfluent/_native.pyi — native extension stubs (for mypy without a build)

Verifying with mypy

pip install mypy
cd crates/pdf-python
mypy --strict --python-path python tests/test_pdfluent_typing.py

Example with typed annotations

from __future__ import annotations
from typing import Optional
from pdfluent import Document, TextSpan, PdfluentError

def get_font(span: TextSpan) -> Optional[str]:
    """Return the font name if available."""
    return span.font_name   # Optional[str] — mypy knows this may be None

def safe_open(path: str) -> Optional[Document]:
    try:
        return Document(path)
    except PdfluentError:
        return None

License Activation

from pdfluent import activate_license, LicenseInfo, PdfluentLicenseError

# Activate from a JSON license string or base64-encoded key
try:
    info: LicenseInfo = activate_license(open("my.license").read())
    print(f"{info.tier} license for {info.company} ({info.seats} seats)")
except PdfluentLicenseError as exc:
    print(f"License error: {exc}")

# Or set the environment variable and call with empty string:
# PDFLUENT_LICENSE_KEY="<base64-key>" python myscript.py
info = activate_license("")   # reads PDFLUENT_LICENSE_KEY from env

LicenseInfo fields: licensee, company, tier, expires_at (Unix timestamp), seats.

Comparison

pdfluent pypdf pdfminer pdfplumber pikepdf
Rendering ✓ (via pdfminer)
Text extraction
Form fill
Redaction
Encryption ✓ (AES-256)
PDF/A validation
Typed stubs partial
Native deps none none none none libqpdf
Language Rust Python Python Python C++

License Activation

The SDK runs in Trial mode by default; output is marked via /Producer metadata. Activate a license to unlock the paid-tier capability set.

import pdfluent

# Activate from a key string
pdfluent.activate_license_key("tier:enterprise")

# Or read the key from a UTF-8 text file
pdfluent.activate_license_file("/path/to/key.lic")

# Inspect the current status (always succeeds; defaults to Trial)
status = pdfluent.license_status()
print(status.tier)              # "Enterprise"
print(status.source)            # "Explicit" | "EnvVar" | "Default"
print(status.output_is_marked)  # False

The PDFLUENT_LICENSE_KEY environment variable is honoured automatically on process start when no explicit activation has happened.

Behavior to be aware of:

  • The active tier is process-global and set-once. Re-activating with the same key is a no-op. Re-activating with a different tier raises RuntimeError; restart Python to switch tiers.
  • Invalid keys raise ValueError; missing license files raise OSError.
  • The key string is never logged or stored beyond the call to activate_license_key.

The 1.0 release accepts the simple evaluation format tier:<name> (trial/developer/team/business/enterprise). Cryptographically signed payloads will be accepted by the same functions in 1.1 without breaking the API.

Building from Source

Requires a Rust toolchain and maturin.

pip install maturin
cd crates/pdf-python
maturin develop --release          # install in current venv
maturin build --release            # build wheel in ./dist/

License

PDFluent Commercial License. See LICENSE.

Links

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pdfluent-1.0.0b17.post4.tar.gz (3.5 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pdfluent-1.0.0b17.post4-cp38-abi3-win_amd64.whl (9.8 MB view details)

Uploaded CPython 3.8+Windows x86-64

pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (9.7 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (8.8 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

pdfluent-1.0.0b17.post4-cp38-abi3-macosx_11_0_arm64.whl (8.4 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

pdfluent-1.0.0b17.post4-cp38-abi3-macosx_10_12_x86_64.whl (9.1 MB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file pdfluent-1.0.0b17.post4.tar.gz.

File metadata

  • Download URL: pdfluent-1.0.0b17.post4.tar.gz
  • Upload date:
  • Size: 3.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for pdfluent-1.0.0b17.post4.tar.gz
Algorithm Hash digest
SHA256 5f8c00a393a7b25fefd0e144d9e56b64fc333c8e315d5dfc3c9f218e9ff4c648
MD5 5940c09c3bdda6f985718d2394a0e526
BLAKE2b-256 af4fa29bb3b82a9788be1c7eae28c22192c33a6825405f45203657e755065374

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfluent-1.0.0b17.post4.tar.gz:

Publisher: build-wheels.yml on jasperdew/xfa-native-rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfluent-1.0.0b17.post4-cp38-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for pdfluent-1.0.0b17.post4-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 0917e2c17810b2bf15bdf026e3620b45c65b048fc460d8dc0de27d51e667c1bc
MD5 167330e6be89f6559583f323a1690cbf
BLAKE2b-256 a3abd9adf1f697f958b3482d92c70d200ff1dc9a1317b679f263e54df678791a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfluent-1.0.0b17.post4-cp38-abi3-win_amd64.whl:

Publisher: build-wheels.yml on jasperdew/xfa-native-rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e3843974d10c3437d6d2c2c2072f17267e240ff9f17764291e4e74fc8f79c701
MD5 15d6614d17f45743c44b493670d01d1c
BLAKE2b-256 c2be0117bbd2f36b969dc2e5af52f6a97cc1b56dbb23559b0376883b77370a78

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: build-wheels.yml on jasperdew/xfa-native-rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 674391a8c6330bb837d5c4569b529e0c324d87e1659c92d2bd2f03abbccb49aa
MD5 a422cef1cd99464a70d1cc740625f38d
BLAKE2b-256 f51ca8e3d55d06bdeed355f124b4f76adc8884152315395b54cff1293f8bfd69

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfluent-1.0.0b17.post4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: build-wheels.yml on jasperdew/xfa-native-rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfluent-1.0.0b17.post4-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pdfluent-1.0.0b17.post4-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 595e8c0bb128938404b9631e82a37b071a423317a89e50f7c35d04bbab516ced
MD5 722481fcdbf7ccd479f2faeb729122a4
BLAKE2b-256 5ca99daecf716efc11468e7b37c407bdfffa2b357e34dc5fd9eefb331b2ab94a

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfluent-1.0.0b17.post4-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: build-wheels.yml on jasperdew/xfa-native-rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pdfluent-1.0.0b17.post4-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for pdfluent-1.0.0b17.post4-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 63b5f99b1866464582fe04d38dcc5a7cf77dad153a632bf5c5c9089bed3f532d
MD5 09969fbf4fe3a17570a4772d5ba90a40
BLAKE2b-256 53f3aa6dbc6ff9f1a1f2d38bd73b20c2e8b0aaf547522a18650418a03f7d36ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for pdfluent-1.0.0b17.post4-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: build-wheels.yml on jasperdew/xfa-native-rust

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page