Skip to main content

privacyforms-pdf

CI Codecov PyPI Python 3.12+ Code style: ruff License: MIT uv

Python library for parsing and filling PDF forms using pypdf.

Features

  • Parse fillable PDFs into a canonical PDFRepresentation schema
  • Fill PDF forms from simple JSON key/value data
  • Extract layout hints and visual row groupings
  • Validate representation JSON against the schema
  • Verify sample data keys against parsed field IDs
  • Extend the CLI through pluggy command entry points

Requirements

  • Python 3.12+
  • pypdf >= 5

Installation

git clone <repo-url>
cd privacyforms.pdf
uv sync

CLI Quick Start

Parse A PDF

pdf-forms parse form.pdf -o representation.json

This writes a compact PDFRepresentation JSON document.

Verify A Representation JSON File

pdf-forms verify-json representation.json

Verify Sample Data Keys Against Parsed Field IDs

pdf-forms verify-data --form-json representation.json --data-json sample-data.json

Preferred format:

  • use field IDs such as f-0 for canonical machine-facing data
  • field names such as Candidate Name remain supported for convenience

Compatibility modes:

  • fill-form accepts --field-keys name|id|auto
  • verify-data accepts --key-mode name|id|auto
  • auto accepts a mixture of field IDs and field names

Fill A PDF Form

pdf-forms fill-form form.pdf data.json -o filled.pdf
pdf-forms fill-form form.pdf data.json -o filled.pdf --no-validate
pdf-forms fill-form form.pdf data.json -o filled.pdf --strict
pdf-forms fill-form form.pdf data.json -o filled.pdf --field-keys id

Recommended fill-form payloads are keyed by field IDs:

{
  "f-0": "John Smith",
  "f-1": "Software Engineer",
  "f-2": "2025-06-01",
  "f-3": true
}

Field names and mixed key styles are still supported through --field-keys name and --field-keys auto.

Check Whether A PDF Contains A Form

pdf-forms info form.pdf

Python API

The package currently exposes two main API layers:

  • read/parse APIs via parse_pdf() and extract_pdf_form()
  • higher-level read/fill/validate APIs via PDFFormService

Parse A PDF Into PDFRepresentation

from privacyforms_pdf import extract_pdf_form

representation = extract_pdf_form("form.pdf")

print(representation.spec_version)
print(representation.source)
print(len(representation.fields))
print(len(representation.rows))

for field in representation.fields:
    print(field.id, field.name, field.type, field.value)

Extract Labels And Nearby Text

Install the optional labels dependency (requires PyMuPDF):

pip install privacyforms.pdf[labels]

CLI:

pdf-forms parse form.pdf --labels -o representation.json

Python API:

from privacyforms_pdf import PDFFormService

service = PDFFormService()
representation = service.extract("form.pdf", extract_labels=True)

for field in representation.fields:
    print(field.title)  # best inferred label
    for block in field.text_blocks:
        print(block.role, block.direction, block.text)

You can also call parse_pdf() directly:

from privacyforms_pdf import parse_pdf

representation = parse_pdf("form.pdf")
json_text = representation.to_compact_json()

Fill And Validate Forms

from privacyforms_pdf import PDFFormService

service = PDFFormService()

has_form = service.has_form("form.pdf")

form_data = {
    "f-0": "John Smith",
    "f-3": True,
}

errors = service.validate_form_data("form.pdf", form_data, key_mode="id")
if errors:
    print(errors)
else:
    service.fill_form("form.pdf", form_data, "filled.pdf", key_mode="id")

You can also fill from a JSON file:

from privacyforms_pdf import PDFFormService

service = PDFFormService()
service.fill_form_from_json("form.pdf", "data.json", "filled.pdf", key_mode="id")

The class also exposes read helpers:

from privacyforms_pdf import PDFFormService

service = PDFFormService()
representation = service.extract("form.pdf")
fields = service.list_fields("form.pdf")
field = service.get_field_by_id("form.pdf", "f-0")
value = service.get_field_value("form.pdf", "Candidate Name")
service.extract_to_json("form.pdf", "representation.json")

Public Objects

Primary exports from privacyforms_pdf:

  • PDFFormService
  • FormFiller
  • parse_pdf
  • extract_pdf_form
  • PDFRepresentation
  • PDFField
  • FieldFlags
  • FieldLayout
  • FieldTextBlock
  • FieldTextRole
  • FieldTextDirection
  • ChoiceOption
  • RowGroup
  • PDFFormError
  • PDFFormNotFoundError
  • FormValidationError
  • FieldNotFoundError

PDFRepresentation Schema

Top-level fields:

  • spec_version: str
  • source: str | None
  • fields: list[PDFField]
  • rows: list[RowGroup]

PDFField

Main fields:

  • name: str
  • title: str | None
  • id: str
  • type: PDFFieldType
  • field_flags: FieldFlags | None
  • layout: FieldLayout | None
  • default_value: str | bool | list[str] | None
  • value: str | bool | list[str] | None
  • choices: list[ChoiceOption]
  • text_blocks: list[FieldTextBlock] — nearby labels, descriptions, helpers
  • format: str | None
  • max_length: int | None
  • textarea_rows: int | None
  • textarea_cols: int | None

Supported field types:

  • textfield
  • textarea
  • datefield
  • checkbox
  • radiobuttongroup
  • combobox
  • listbox
  • signature

FieldLayout

Layout hints are stored in integer PDF coordinates:

  • page: int | None
  • x: int | None
  • y: int | None
  • width: int | None
  • height: int | None

FieldTextBlock

Nearby text associated with a field (populated when extract_labels=True):

  • text: str
  • role: "label" | "description" | "helper" | "instruction" | "unknown"
  • direction: "left" | "right" | "above" | "below" | "inside" | "unknown"
  • layout: FieldLayout | None
  • distance: float | None

RowGroup

Visual rows derived from layout analysis:

  • fields: list[PDFField | str]
  • page_index: int

When serialized, row fields are emitted as field IDs.

JSON Shape

Example parsed representation:

{
  "source": "form.pdf",
  "fields": [
    {
      "name": "Candidate Name",
      "id": "f-0",
      "type": "textfield",
      "layout": {
        "page": 1,
        "x": 53,
        "y": 1077,
        "width": 361,
        "height": 27
      }
    }
  ],
  "rows": [
    {
      "fields": ["f-0"],
      "page_index": 1
    }
  ]
}

Notes:

  • omitted fields are intentionally excluded by compact serialization
  • field_flags only serializes flags set to true
  • rows reference fields by ID in JSON

Exceptions

  • PDFFormError: base exception for form-related failures
  • PDFFormNotFoundError: raised when a PDF does not contain a form
  • FormValidationError: raised when fill-time validation fails
  • FieldNotFoundError: exported for compatibility and field lookup failures

Ratings

Aspect Score Notes
Overall 9/10 Production-grade library with excellent engineering discipline
Security 9/10 Input validation, symlink rejection, size limits, Bandit clean
Architecture 9/10 Clean layers, canonical schema, pluggy extensibility
API Design 8/10 Dual function/class layers, type-safe, minor wrapper leakage
Functionality 9/10 All form types handled, cross-generator radio support, graceful fallback
Code Quality 9/10 100% coverage, strict ruff/ty, complete type hints
Documentation 8/10 Excellent project docs; PDF internals could use more inline depth

Security

  • Symlinks are rejected for both reads and writes to prevent path-traversal issues
  • PDF files are validated via magic-byte header check (%PDF) before parsing
  • Input size limits guard against oversized PDFs (> 50 MB) and JSON (> 10 MB)
  • JSON depth limits prevent stack exhaustion from malicious payloads

Architecture

  • Clean separation of concerns: schemaparserfillerextractorcli
  • Canonical PDFRepresentation schema (Pydantic v2) is the single source of truth
  • CLI commands are loaded dynamically via pluggy entry points — easy to extend
  • Low-level PDF writer (FormFiller) is decoupled from the high-level service (PDFFormService)

API Design

  • Two complementary layers: function-based (parse_pdf, extract_pdf_form) and class-based (PDFFormService)
  • Field IDs are the canonical key format; field names remain supported for convenience
  • key_mode="auto" accepts mixed payloads of IDs and names
  • All public methods have complete type hints and Google-style docstrings

Functionality

  • Handles all common PDF form types: text, textarea, date, checkbox, radio, combo, listbox, signature
  • Radio button state resolution works across different PDF generators
  • Listbox filling includes custom appearance streams so selections are visible in viewers
  • Graceful fallback when pypdf's appearance-stream generation hits edge cases

Code Quality

  • 100% test coverage (426 tests) with pytest and pytest-cov
  • Ruff enforces strict linting (E, W, F, I, N, D, UP, B, C4, SIM, TCH)
  • ty type checker runs in strict mode — complete type hints throughout
  • Bandit security scanner integrated; no high or medium severity issues

Development

Quality Checks

make check
make test
make test-cov

Project Structure

privacyforms.pdf/
├── privacyforms_pdf/
│   ├── __init__.py
│   ├── schema.py
│   ├── schema_layout.py
│   ├── parser.py
│   ├── extractor.py
│   ├── filler.py
│   ├── hooks.py
│   ├── cli.py
│   └── commands/
├── tests/
├── samples/
├── demo/
├── docs/
├── pyproject.toml
└── README.md

License

MIT

Release files for privacyforms.pdf 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for privacyforms.pdf 0.2.0
File Size Uploaded
privacyforms_pdf-0.2.0.tar.gz 66.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for privacyforms.pdf 0.2.0
File Interpreter ABI Platform
privacyforms_pdf-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 112.5 kB

Release files / privacyforms_pdf-0.2.0.tar.gz

Download URL privacyforms_pdf-0.2.0.tar.gz
Size 66.5 kB
Tags Source
SHA-256 checksum
How to use checksums
ffc436db85c68e67fdd69c14abdc21f4b1318af6ed9fa77a806cc4f463be38f1
BLAKE2b-256 checksum
How to use checksums
6daef4f2db1f5ca6ad964a5d8dd60f957f1e212e05a9d987d0a56490e85614d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.

Transparency log

Release files / privacyforms_pdf-0.2.0-py3-none-any.whl

Download URL privacyforms_pdf-0.2.0-py3-none-any.whl
Size 46.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
06295939db70c8cee4826c5e625dbf442b99e50ed1c3127075fe3897229c63c8
BLAKE2b-256 checksum
How to use checksums
55c890bf461adb3ea59d83e3c3052910abd821f061160a0bbdc2f532d0e3ca66
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

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