Guide
Fill a PDF in Python
If you need to fill a PDF form from Python, you have two honest options: drive a native library like pypdf or fillpdf, or POST the template to an HTTP API and get the filled PDF back. This page shows the HTTP path in full, then makes the case for when the native library is the better call — because sometimes it is.
The fastest path: one requests.post
PDFops fills AcroForm fields server-side on the edge. From Python it is one request — no pdftk, no poppler, nothing to install beyond requests:
import json, requests
with open("template.pdf", "rb") as f:
resp = requests.post(
"https://pdfops.dev/api/fill-form",
files={"pdf": f},
data={"fields": json.dumps({
"customer_name": "Acme Co",
"invoice_total": "$1,250.00",
"paid": "Yes",
})},
)
resp.raise_for_status()
with open("filled.pdf", "wb") as out:
out.write(resp.content)
The field names in the dict must match the AcroForm field names in the template. If you are not sure what those are, drop the PDF into the Form-Field Inspector — it lists every field name and type, so your keys line up on the first try. No API key or signup is required during beta.
Doing it natively: pypdf and fillpdf
The pure-Python route is real and worth knowing. pypdf fills AcroForm fields without any system binaries:
from pypdf import PdfReader, PdfWriter
reader = PdfReader("template.pdf")
writer = PdfWriter()
writer.append(reader)
writer.update_page_form_field_values(
writer.pages[0],
{"customer_name": "Acme Co", "invoice_total": "$1,250.00"},
auto_regenerate=False,
)
with open("filled.pdf", "wb") as out:
writer.write(out)
This works, and for many templates it is all you need. The friction shows up at the edges: some viewers won’t render filled values until you set the NeedAppearances flag or regenerate appearance streams; checkboxes and radio groups need the exact on-state name, not True; and embedded-font edge cases can drop characters. None of these are dealbreakers — they are simply PDF-internals work you now own.
fillpdf wraps pdfrw with a friendlier API and can flatten, but flattening pulls in pdftk (and often poppler), which is the usual snag inside containers, Lambda, and other serverless runtimes where shipping system binaries is awkward.
When PDFops fits, when a local library fits
| PDFops (HTTP) | pypdf / fillpdf (local) | |
|---|---|---|
| Native dependencies | None — just requests | pypdf: none; fillpdf: pdftk/poppler to flatten |
| Serverless / edge friendly | Yes — nothing to provision | pypdf: yes; fillpdf: painful (binaries) |
| Network call required | Yes — one POST per fill | No — fully local/offline |
| Appearance-stream quirks | Handled server-side | You own NeedAppearances, fonts, checkboxes |
| Merge in the same tool | Yes — /api/merge | pypdf can merge; fillpdf cannot |
| Determinism | Deterministic, audit-safe | Deterministic (no AI either) |
| Best for | Serverless backends, no-binary stacks, fill+merge at scale | Offline jobs, no-network constraints, full local control |
The honest summary: if your code already runs somewhere you control with no network constraint and you don’t mind owning PDF internals, pypdf is a fine, free, dependency-light choice. If you are on serverless/edge, want fill and merge behind one deterministic API, or simply don’t want to debug appearance streams, the HTTP call earns its keep.
Merging PDFs in Python too
The same primitive merges. Concatenate several PDFs into one in a single call:
import requests
files = [("pdfs", open(p, "rb")) for p in ["a.pdf", "b.pdf", "c.pdf"]]
resp = requests.post("https://pdfops.dev/api/merge", files=files)
resp.raise_for_status()
open("merged.pdf", "wb").write(resp.content)
A common shape is fill-then-merge: fill several AcroForm templates from your Python backend, then merge the results into one document to deliver. Both halves are the same deterministic primitive, so the combined output is reproducible end to end. The endpoint details are in the fill-form docs and the merge docs.
Frequently asked
How do I fill a PDF form in Python?
Either drive a native library (pypdf, fillpdf) that writes AcroForm values from Python, or POST the template plus a JSON map of field values to an HTTP API and get the filled PDF back. The native route is fully local; the HTTP route needs no system binaries, which is why it suits serverless and edge runtimes where installing pdftk or poppler is painful.
Should I use pypdf or PDFops?
pypdf when you want zero network calls and full local control and are happy handling appearance-stream quirks yourself. PDFops when you are on serverless/edge with no easy way to ship binaries, want one deterministic API for fill and merge, or would rather not own the PDF-internals edge cases. Many teams prototype with pypdf and move the hot path to the API once the quirks cost time.
Can I fill a PDF in Python without pdftk or poppler?
Yes. pypdf is pure Python and needs no binaries to fill. fillpdf needs pdftk/poppler to flatten, which is the usual container/serverless snag. The PDFops API needs nothing installed locally — the fill runs server-side — so it is a common pick exactly when pdftk and poppler are awkward to provision.
Is the fill deterministic?
Yes — the same template plus the same field values yields the same field-level output, with no AI inference in the path. Outputs are diffable and audit-safe, which matters for regulated documents where identical inputs must produce identical PDFs. The longer argument is in this essay.
What if my PDF has no form fields?
Filling requires AcroForm fields to exist. If a template has none, add them once in Acrobat, Mac Preview, LibreOffice Draw, or pdftk, then fill that template repeatedly. The free Inspector lists the field names a PDF exposes so your Python dict keys match them.
Try it in 30 seconds
No API key, no signup during beta. Fill a real template right now with the snippet above, or explore fields visually in the playground. Filling from another stack? See fill a PDF in Node and fill a PDF in JavaScript.
If the deterministic fill + merge primitive fits your usage, join the waitlist and tell me the forms you fill most — that signal is what the pricing tiers and the in-function library get built around.