Library / SDK
pypdfium2-team/pypdfium2 avatar
pypdfium2-team/pypdfium2

pypdfium2: Python bindings to PDFium, and the build system behind them

Python bindings to PDFium, reasonably cross-platform.

841 stars54 forksPythonLicense varies

At a glance

What is it?
pypdfium2 binds Python to the PDFium library at the ABI level, generated from the PDFium headers by a fork of ctypesgen. Installing it is usually one pip command, but the interesting part of the repository is what happens on the platforms without a pre-built wheel, where the package can use a system PDFium, or build one from source, and verify the binaries it downloads.
Who is it for?
Adopt pypdfium2 if you need PDF rendering or inspection from Python and want a binding that exposes the raw API rather than hiding it behind a wrapper, since the raw PDFium interface stays reachable next to the convenience helpers. Do not adopt it if you need a stable setup interface, because the README says the setup is not bound by API stability promises and may change at any time.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 12 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 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

An ABI-level binding, which is a deliberate choice

pypdfium2 is described as an ABI-level Python 3 binding to PDFium, which is the library behind the Chrome and Edge PDF viewers, described in the README as a capable and liberally licensed library for PDF rendering, inspection, manipulation and creation.

The word ABI-level is the whole design. This is not a hand written wrapper with Python classes per API call. The bindings are generated by ctypesgen, a fork of which the project maintains, from the PDFium headers, and the result is a direct mapping of the C interface. The README says explicitly that the helpers are there to simplify common use cases while the raw PDFium API remains accessible through ctypes.

That has a clear consequence for how you should use it. The helpers cover the things almost everybody needs, so a normal script should not have to think about the C layer. But when you need something the helpers do not cover, the underlying API is right there, and a project that is willing to work at the C level gets the full surface of PDFium without waiting for a Python binding to grow.

The cost of the approach is that the binding is generated rather than designed, so the ergonomics are uneven. A generated API is faithful and complete, and it is rarely pleasant, and the helper layer exists precisely to paper over that.

The project is developed by a team with a repository of their own, the documentation is on readthedocs, and there is a platform support page listing the platforms with pre-built packages. The releases are frequent, with 5.13.0 in August 2026, a beta of 5.14.0 in early October and 5.14.0 a couple of days later, and the last push was on 2026-10-15.

One command installs it, and the fallbacks are the interesting part

The recommended install is the shortest line in the README:

bash
python -m pip install -U pypdfium2

If a pre-built wheel exists for the platform, that is what gets installed. If not, the setup code runs instead, and that is where the design decisions are.

The setup will look for a system PDFium, and it can also build PDFium from source. The system route is selected with an environment variable:

bash
PDFIUM_PLATFORM="system-search" python -m pip install -v .

That route probes for a system shared library using the standard portable library search, and the result is hardcoded into the generated bindings, so the library you find at install time is the one you get at run time. There is also an environment variable to point at an out-of-tree shared library directly.

The download route is the default, and it has a security dimension that the README takes seriously. The binary distribution project publishes GitHub build provenance attestations, and the setup can verify them, which is why the documentation recommends installing the GitHub command line interface, at a stated minimum version, for the setup to check authenticity of the binaries it downloads. That is a meaningful thing for a package that fetches a compiled library during installation.

The pip options are documented in detail, and two of them have sharp edges the README admits to. The option to avoid binary wheels is described as improperly named, because the setup will use binaries anyway, and the environment variable for forcing a source distribution or a fallback is the real control. The option to install a beta release is a single flag.

The setup is explicitly not covered by API stability

One note in the README deserves to be quoted rather than paraphrased, because it governs how you should pin this package.

The note says that, unlike the helpers, the setup of pypdfium2 is not bound by API stability promises, so it may change at any time.

The reason is the second note, which explains the relationship between the package and the ctypesgen fork it depends on. The two are developed in sync, so each pypdfium2 commit is expected to be coupled with the then current head of the fork. Release source distributions and the latest version from git will automatically use the matching ctypesgen. But if you check out a commit that is not the latest, you have to set up the right ctypesgen version yourself, and for release commits the revision that was used is recorded in a file in the release tooling.

For a project that pins a released version, this is handled for you. For a project that tracks a branch, it is your problem, and the recorded revision is the only way to solve it.

There is a related note about the JavaScript and XFA builds, which need a different flavour of the PDFium binaries, the ones built with the V8 JavaScript engine and XFA support. The example installs with the platform variable set to that flavour and the binary wheel option turned off, which forces the setup path to run and pick the right build.

So the picture is: a stable, helpful API on top, and a setup layer underneath that is explicitly allowed to move, because it is the part that has to track a C library and a code generator at the same time.

Optional dependencies, imported only when you need them

The runtime dependency story is unusually tidy, and the README is explicit about it.

As of writing, the package requires no mandatory runtime dependencies apart from Python and PDFium itself, which is commonly bundled. The optional ones exist for specific features.

Pillow is the imaging library, and the package provides convenience adapters that translate between raw bitmap buffers and Pillow images, and it uses Pillow for some command line functionality such as saving images. NumPy is used the same way, with a helper that gives a NumPy array view of a raw bitmap. OpenCV can be used in the rendering command line to save through the NumPy adapter. And tabulate, if it happens to be installed, makes the command line output prettier where tables are involved.

The detail that makes this arrangement pleasant is the last one in the section: the package defers imports of the optional dependencies until they are actually needed, so there is no startup overhead if you do not use them. For a library that gets imported in a script, a service or a test suite, that difference between an unconditional import of NumPy and Pillow and a deferred one is measurable, and the design here is the deferred kind.

The command line tool is worth noting as well, because it is where the optional dependencies actually earn their place. There is a module for it, and the build configuration runs its help and version commands as part of the wheel test, which means a broken command line entry point fails the build rather than the installation.

The build system is a small build system

The packaging code in this repository is more elaborate than most projects of its size, and reading it tells you what the distribution actually is.

The setup file defines a custom distribution class that declares the package to have extension modules, so that the modules are not nested in a pure library subdirectory inside the wheel, and so that the wheel is marked as platform specific and not pure Python. There is a comment about older setuptools versions not honouring that, which is the kind of detail you only find in a project that has been fighting with wheel tags for a while.

On top of that there is a custom build command for the build step, and a small package of setup helpers in a separate directory, with modules for finding a system PDFium, for preparing the setup, and for computing the wheel tag. The package data is generated, and the wheels carry the compiled library alongside the Python code.

The project file shows the rest. The build backend is setuptools with a recent version, and the dependency groups are carefully split: converters for the imaging libraries, test for pytest with the converters, setup for the build tooling with a platform-specific dependency on the Mach-O library handling, docs for Sphinx with several extensions, and a check group with autoflake, codespell, the REUSE licence tool and zizmor, which audits the GitHub Actions workflows. There is also a distribution check group with twine and a wheel contents checker, and the wheel checker has three deliberate exclusions for cases where the wheel legitimately contains several top level entries and a directory with no Python modules, which is where the bundled native library lives.

The build backend configuration for the wheel builder passes through environment variables that control which PDFium is used, and it runs the command line tool and a small test script as the wheel test. And there is a note explaining why one particular manylinux image is unfit, because the GN build dependency fails to compile there under C++23, which is a level of detail that only matters if you are producing wheels for that platform.

Licences are tracked as files, and the checks are part of the build

The licence situation in this repository is more careful than the listing suggests, and it is worth reading properly because the listing records the licence as unknown while the files say otherwise.

The project file carries an SPDX identifier of Apache-2.0 or BSD-3-Clause for the code, the readme has its own header with a Creative Commons attribution identifier, and the repository contains directories for build licences and licences, a manifest for the distribution, a reuse configuration file and a file recording the licence of the bundled third-party files. The readme also carries a copyright line in an SPDX comment form at the top, which is the convention the REUSE tool expects.

So the pattern is that every file, including the ones that are not source code, has a licence recorded, and there is a tool in the check group whose job is to verify that. The check group is otherwise autoflake for unused imports, codespell for spelling and zizmor for auditing the workflow files with an auditor persona, which is a sensible set of checks for a project whose install process downloads binaries and builds native code.

The developer commands live in a justfile rather than a Makefile, and they cover the same ground: the test target runs pytest over the tests directory, the clean target removes a long list of build and cache directories, the pull target updates both the repository and the ctypesgen dependency, the check target runs a shell script, the distribution check runs twine and the wheel contents checker, and the documentation is built with Sphinx and served in a browser.

So a project that began as a way to call PDFium from Python has grown a release pipeline, a licence audit and a security audit, and the README is the thin tip of that.

Editorial conclusion

Adopt pypdfium2 if you need PDF rendering or inspection from Python and want a binding that exposes the raw API rather than hiding it behind a wrapper, since the raw PDFium interface stays reachable next to the convenience helpers. Do not adopt it if you need a stable setup interface, because the README says the setup is not bound by API stability promises and may change at any time. Before you pin a version, remember that the package and its ctypesgen fork are developed in sync, so a non-latest commit means you have to match the ctypesgen revision yourself, and if you build from source, install the GitHub CLI so the setup can verify the downloaded binaries' attestations.

Frequently asked questions

What is pypdfium2?

It is an ABI-level Python 3 binding to PDFium, the library used by Chrome and Edge for PDF rendering, inspection, manipulation and creation. The bindings are generated from the PDFium headers with a fork of ctypesgen, and helper functions are provided on top while the raw API stays reachable.

How do I install pypdfium2?

With python -m pip install -U pypdfium2, which uses a pre-built wheel where one exists. Otherwise the setup runs, downloading a PDFium binary and generating the bindings, or looking for a system PDFium, or building PDFium from source. The setup also verifies the downloaded binaries' attestations when the GitHub CLI is installed.

Does pypdfium2 need any runtime dependencies?

Apart from Python and PDFium itself, no mandatory runtime dependencies. Pillow, NumPy, OpenCV and tabulate are optional and used for image conversion, array views, command line output and prettier tables, and the package defers their imports so there is no startup cost if you do not use them.

Is the pypdfium2 setup stable?

The README says the opposite, explicitly: unlike the helpers, the setup is not bound by API stability promises and may change at any time. The package and its ctypesgen fork are developed in sync, so a commit that is not the latest needs a matching ctypesgen revision set up by hand, with the revision used for a release recorded in the release tooling.

How is pypdfium2 released and checked?

The project file splits its tooling into dependency groups for testing, setup, documentation and checks, the last including autoflake, codespell, the REUSE licence tool and zizmor for auditing the workflows. The wheel build runs the command line tool and a small test script, and the developer commands live in a justfile rather than a Makefile.

Official sources

  1. Issues
  2. Project website
  3. pypdfium2-team/pypdfium2 on GitHub
  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/pypdfium2-team-pypdfium2.svg)](https://hysenlabs.com/projects/pypdfium2-team-pypdfium2)

Was this useful?