CLI tool
chinapandaman/PyPDFForm avatar
chinapandaman/PyPDFForm

PyPDFForm: filling and inspecting PDF forms from Python or the shell

🔥 The Python library & CLI for PDF forms.

1,247 stars71 forksPythonMIT

At a glance

What is it?
PyPDFForm is a Python library and command line tool for creating, inspecting, styling, and filling PDF forms, plus the page extraction and merging utilities that come with it. It wraps pikepdf, pypdf, reportlab, Pillow, fonttools, and cryptography, and it keeps a visual diff workflow for checking form rendering against a reference.
Who is it for?
Adopt PyPDFForm if your workflow reads or writes AcroForm data rather than just rasterising pages, and if you want one dependency set for both a Python API and a shell tool. Do not adopt it as a general PDF text extraction library, since that is not what the form model is for.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A form library that also answers to the shell

PyPDFForm is described as a Python library and command line tool for working with PDF forms, and the project summary puts it as the library and CLI for PDF forms. The stated goal is to make PDF form work straightforward whether you are handling a single document or building a larger workflow.

The capability list is wider than filling in boxes. You can create PDF forms, form fields, and raw elements; inspect form fields, metadata, and values; update field styling, behaviour, and scripts; fill PDF forms; and extract pages and merge PDFs. That last line matters more than it first appears. Page extraction and merging are not form operations, but they are the operations that surround them in any document workflow, and having them in the same tool with the same object model saves a second dependency for a job that is a few lines of code.

The package is on PyPI as PyPDFForm, MIT licensed, authored by Jinge Li, and the class it exports is PdfWrapper. The project also keeps a documentation site at chinapandaman.github.io/PyPDFForm with per-version URLs, and the README links to a recorded demo, a video tutorial playlist, and a Chicago Python User Group talk, so there is a walkthrough to follow before reading the API reference.

Two installs for two audiences

The library and the CLI are installed differently, and the difference is deliberate. As a library, the base package is enough:

shell
pip install PyPDFForm

The command line tool lives behind a cli extra, and the README recommends installing that with pipx:

shell
pipx install "PyPDFForm[cli]"

The pipx approach puts the CLI in its own environment rather than in the project virtualenv, which is the standard advice for a command line tool that a person runs from a shell. The cli extra pulls in typer for the command line interface, jsonschema, and PyYAML, and the two latter dependencies hint at how the CLI is configured, since JSON Schema and YAML are what you would use to describe a form filling job as data.

The core library dependency set is more interesting. It includes pikepdf for the low level PDF object model, pypdf for higher level document handling, reportlab for generation, Pillow for image handling, fonttools for font work, and cryptography. Those are pinned to narrow ranges, for example pikepdf>=10.7.2,<11.0.0 and pypdf>=6.12.2,<7.0.0, so a major bump of a transitive dependency will not silently change your output.

The package requires Python 3.11 or newer, and the classifiers list support through Python 3.15.

Filling a form with PdfWrapper

The quick example is short enough to read in one pass, and it shows the shape of the API. You construct a PdfWrapper around the template, call fill with a dictionary of field values, and write the result:

python
from PyPDFForm import PdfWrapper

filled = PdfWrapper("sample_template.pdf", need_appearances=True).fill(
    {
        "test": "test_1",
        "check": True,
        "test_2": "test_2",
        "check_2": False,
        "test_3": "test_3",
        "check_3": True,
    },
)

filled.write("output.pdf")

Two things stand out. The dictionary is keyed by field name, and the values are typed, so a checkbox is a Python boolean rather than a string like the letters Y or N. The fill method returns a new object rather than mutating in place, and write is a separate call, which means you can fill, inspect, and adjust before committing a file to disk.

The need_appearances=True argument is the subtle one, and it is the reason the example works. A PDF form field holds a value, and the visible appearance of that value on the page is a separate artefact that some viewers recompute and others do not. Setting this flag tells the document that appearances need to be regenerated, so a viewer that will not do it itself still shows the filled state. The sample template and the expected filled output are both published with the documentation, so you can compare your result against a reference.

The same class covers the other operations the project lists, including inspection of fields, metadata, and values, updating styling, behaviour, and scripts, and extracting pages.

The visual diff workflow in the Makefile

The repository contains something unusual for a Python library, which is a set of make targets for visual regression testing of PDF output. The Makefile is a list of shell scripts under scripts/, each with a descriptive name:

make
compare-pdf-diffs:
	bash ./scripts/pdf_diffs.sh

compare-pdf-diffs-acrobat:
	ACROBAT=true bash ./scripts/pdf_diffs.sh

compare-widget-diffs:
	bash ./scripts/diff_widget.sh $(F1) $(F2) $(KEY) $(LV)

This tells you more about how the project is maintained than a coverage badge does. Rendering a form to an image and comparing it against a stored reference catches the class of bug that no unit test finds, namely that a field moved two millimetres, a font fell back, or a value overflowed its box. The widget diff script takes two documents, a key, and a level, so it compares one specific widget in one specific state rather than the whole page, which is what makes a failure readable.

The ACROBAT=true variant switches the comparison path to Adobe Acrobat, so the same document can be checked in two different renderers. That is the point of the exercise. A PDF is a container for drawing instructions, and two viewers can legitimately draw the same form differently. Rendering with a poppler based tool and with Acrobat and comparing tells you whether the difference is in your document or in the viewer.

The other targets support the same workflow. generate-new-pdf-samples regenerates the reference set, serve-files starts a local HTTP server, and clean-temp removes the temporary PDFs and PNGs the diffing produces.

What the repository layout reveals

The sample directories are the most revealing part of the tree. Alongside pdf_samples/ there are font_samples/, image_samples/, js_samples/, json_samples/, and yaml_samples/, and the js_samples directory name is consistent with the feature where you update field behaviour and scripts, meaning a PDF form that carries JavaScript actions can be inspected and modified alongside static field data.

The JSON and YAML sample directories line up with the CLI extra's jsonschema and PyYAML dependencies. A form filling job expressed as a JSON or YAML document, validated against a schema, is a different shape from a Python script, and it is the shape that fits a scheduled job or a template-driven pipeline where the same form is filled with different values every day. The CLI is the interface to that.

The build and test tooling is script-driven rather than pytest-driven at the top level, with build-all, test-all, coverage-all, linting, and format-code each wrapping a script, and the dev extra covering coverage, mkdocs with the material theme, mike for versioned docs, pylint, pyright, pytest, and pudb as a debugger. The workflow badge reports 100% coverage. A Dockerfile is also present, based on python:3.13-slim, with uv installed, poppler-utils and imagemagick among the system packages, and a non-root user, which is what a documentation and comparison tool needs since rendering to images is part of the job.

Recent releases came in quick succession: v6.0.1 on 2026-10-15, v6.0.0 on 2026-10-10, and v5.6.0 on 2026-09-12, and the last push to the repository was on 2026-10-18. The 6.0.0 release is a major version bump, which by convention signals breaking API changes.

Against writing raw pypdf code, and against a browser form service

The alternative for most Python developers is to use pypdf directly. Since PyPDFForm depends on pypdf anyway, the comparison is fair, and the difference in approach is the layer above the document. Raw pypdf gives you the AcroForm dictionary and you set field values by walking the tree yourself, then you deal with appearances, then you handle the specific quirks of each template. PyPDFForm gives you a wrapper object with named methods for the operations, a typed value mapping for filling, and a flag for the appearance problem, and it adds page extraction and merging in the same object model.

What you give up for that is control over the raw object graph, and you pay in dependencies. Six libraries for the base install, each pinned, is a heavier footprint than a single pypdf dependency, and if your use case is one field in one template, the wrapper is more than you need.

The other alternative is a browser based service, where a form is rendered in a browser and submitted to a server. The difference there is not technical but architectural. A browser service needs a rendering engine and a server, and the document you get back is produced by that service rather than by the tool that owns the template. A local library keeps the template and the data on the machine that already holds them, which is what you want when the form is a tax document, a contract, or anything with a signature on it.

The one thing none of the alternatives give you for free is the appearance problem, and it is worth understanding rather than copying. A field value without a matching appearance stream is a value the PDF file carries but the screen may not show, which is why the example passes need_appearances=True rather than leaving it off.

Editorial conclusion

Adopt PyPDFForm if your workflow reads or writes AcroForm data rather than just rasterising pages, and if you want one dependency set for both a Python API and a shell tool. Do not adopt it as a general PDF text extraction library, since that is not what the form model is for. Verify first that your templates are AcroForm documents, because the need_appearances flag exists precisely to rebuild field appearances for viewers that do not compute them.

Frequently asked questions

How do I install PyPDFForm for use as a library and as a CLI?

As a library, run pip install PyPDFForm. The command line tool is behind the cli extra, which the README recommends installing with pipx using pipx install "PyPDFForm[cli]". The extra brings in typer, jsonschema, and PyYAML.

What does need_appearances do in PyPDFForm?

The quick example constructs PdfWrapper with need_appearances=True, which tells the document that field appearances need to be regenerated so viewers show the filled values. The README publishes a sample template and the expected filled output so you can compare against a reference.

Which Python versions and dependencies does PyPDFForm support?

The package requires Python 3.11 or newer, with classifiers through Python 3.15. Core dependencies are pikepdf, pypdf, reportlab, Pillow, fonttools, and cryptography, each pinned to a narrow version range.

Can PyPDFForm do more than fill forms?

Yes. The project lists creating PDF forms, form fields, and raw elements, inspecting form fields, metadata, and values, updating field styling, behaviour, and scripts, filling forms, and extracting pages and merging PDFs.

How does the PyPDFForm project check that rendered PDFs look right?

The Makefile wraps shell scripts for comparing PDF diffs and widget diffs, with an ACROBAT=true variant that switches the comparison path to Adobe Acrobat. A generate-new-pdf-samples target regenerates the reference set, and the workflow badge reports 100% coverage.

Official sources

  1. chinapandaman/PyPDFForm on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chinapandaman-pypdfform.svg)](https://hysenlabs.com/projects/chinapandaman-pypdfform)

Was this useful?