Goal
Produce a correctly filled PDF (new file) from a blank AcroForm template and structured user data, without relying on browser automation for the PDF viewer.
Do not use browser automation for in-browser PDFs
Chrome (and similar) render PDFs inside an internal viewer with no HTML form controls. Playwright/Cypress cannot reliably target AcroForm fields there.
Preferred path: download or use a local .pdf, then fill with pypdf via uv run.
Workflow
- Confirm the PDF is AcroForm
- Run field listing (see below). If get_fields() is empty and the doc still looks interactive, it may be XFA-only — say so and propose Adobe/manual or a specialized XFA tool; do not pretend pypdf will work.
- Inventory fields
- Use reader.get_fields() for writer-safe keys (e.g. dotted child names). - Walk page /Annots widgets for /TU (tooltip) → human label when /T is opaque. - Bundled helper from this skill (run from repo root or pass absolute path):
``bash uv run --with pypdf --with cryptography python skills/pdf-acroform-fill/scripts/listpdfform_fields.py /path/to/form.pdf ``
- Map data → field names
- Build a single dict of {fieldname: value}. - Checkboxes / radios: inspect /AP /N keys for the checked export value (/On, /Oui, etc.). Wrong export = box stays off. - Repeating rows: often encoded as parent field + indexed kids; use keys exactly as getfields() returns them. - Dates: ask or follow user preference (e.g. YYYY-MM-DD). Tooltip text hints are not always authoritative.
- Write the filled PDF
```python from pypdf import PdfReader, PdfWriter
reader = PdfReader("template.pdf") writer = PdfWriter() writer.append(reader) merged = {textfields, buttonfields} for page in writer.pages: writer.updatepageformfieldvalues(page, merged, auto_regenerate=False) writer.write("filled.pdf") ```
- Deliver
- Save filled.pdf to a clear path. - Prefer a small, repeatable script next to the project if the user will regenerate (template path, constants, item list). - Summarize non-obvious mappings (especially checkboxes and row indices).
When the user only has a URL
- Fetch or ask them to download the PDF; then fill locally.
- Do not promise Playwright will type into the embedded viewer.
Reference
See references/pypdf-filling.md for limitations and the write pattern.
Evals
Prompts for regression testing live in evals/evals.json. Extend with assertions when outputs are machine-checkable (field values in output PDF).