# Pdfium Impl Form Fields

> Use when reading or filling PDF form fields with pdfium-render: detecting an embedded form, listing field values, setting a text field, checking a checkbox, or selecting a radio button. Prevents the Adobe-Acrobat-blank-field trap, the wrong PdfFormFieldText type name, passing a bool to a radio button, and rendering filled forms without render_form_data. Covers PdfDocument::form, PdfForm, PdfFormField and its eight variants, as_form_field / as_form_field_mut, set_value, set_checked, field_values, and the 0.7.34 to 0.9.x version differences. Keywords: pdfium-render form fields, fill PDF form, fill out a PDF, read PDF form values, PdfForm, PdfFormField, PdfFormTextField, PdfFormCheckboxField, PdfFormRadioButtonField, as_form_field, as_form_field_mut, set_value, set_checked, field_values, render_form_data, AcroForm, XFA, Widget annotation, filled form blank in Acrobat, form field empty after flatten, form fields not showing, PdfFormFieldText not found, how do I fill a PDF form

- Skill: `impertio-studio/pdfium-impl-form-fields` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/pdfium-impl-form-fields`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/pdfium-impl-form-fields/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/pdfium-impl-form-fields

---


# pdfium-impl-form-fields

Read and fill interactive PDF form fields with pdfium-render: detect an embedded
form, enumerate fields, set text values, toggle checkboxes, select radio
buttons, render the filled result, and save.

Scope: form fields specifically. For non-field annotations (highlights, stamps,
links) see `pdfium-impl-annotations`. For the render pipeline see
`pdfium-syntax-rendering`. For saving see `pdfium-impl-saving`.

Default API surface is 0.9.x. Version traps for 0.8.x are called out inline.

## The one rule that dominates this area

pdfium-render fills a field's VALUE but does NOT regenerate its appearance
stream. Adobe Acrobat paints fields from the appearance stream, so a
programmatically filled field can show correctly in Chrome and pdfium-render's
own renderer yet appear BLANK in Acrobat (issue #145). ALWAYS verify filled
output in the target viewer. See `references/anti-patterns.md` section 1.

## Quick Reference

### The access path

Form fields are NEVER a flat collection. The path is fixed:

```
PdfDocument
  -> form() -> Option<&PdfForm>                  (does a form exist?)
  -> pages().iter() -> PdfPage
       -> annotations().iter() -> PdfPageAnnotation   (owned values)
            -> as_form_field()      -> Option<&PdfFormField>
            -> as_form_field_mut()  -> Option<&mut PdfFormField>
                 -> as_text_field_mut()         -> PdfFormTextField
                 -> as_checkbox_field_mut()     -> PdfFormCheckboxField
                 -> as_radio_button_field_mut() -> PdfFormRadioButtonField
```

Each field is wrapped inside a page annotation of type `Widget` or `XfaWidget`.
`as_form_field()` returns `None` for every other annotation type.

### Core API

| Goal | Call | Returns |
|------|------|---------|
| Does the document have a form? | `document.form()` | `Option<&PdfForm<'_>>` |
| Read every field value at once | `form.field_values(document.pages())` | `HashMap<String, Option<String>>` |
| Get a field from an annotation | `annotation.as_form_field()` | `Option<&PdfFormField<'_>>` |
| Get a mutable field | `annotation.as_form_field_mut()` | `Option<&mut PdfFormField<'a>>` |
| Identify a field's kind | `field.field_type()` | `PdfFormFieldType` |
| Read a text value | `text_field.value()` | `Option<String>` |
| Set a text value | `text_field.set_value(&str)` | `Result<(), PdfiumError>` |
| Read a checkbox / radio state | `field.is_checked()` | `Result<bool, PdfiumError>` |
| Check a checkbox | `checkbox.set_checked(bool)` | `Result<(), PdfiumError>` |
| Select a radio button | `radio.set_checked()` | `Result<(), PdfiumError>` |
| Show filled values in a render | `PdfRenderConfig::render_form_data(true)` | `Self` |

Full signatures and version annotations: `references/methods.md`.

### PdfFormField variants

| Variant | Wrapped struct | Mutable narrowing? |
|---------|----------------|--------------------|
| `Text` | `PdfFormTextField` | yes (`as_text_field_mut`) |
| `Checkbox` | `PdfFormCheckboxField` | yes (`as_checkbox_field_mut`) |
| `RadioButton` | `PdfFormRadioButtonField` | yes (`as_radio_button_field_mut`) |
| `ComboBox` | `PdfFormComboBoxField` | no |
| `ListBox` | `PdfFormListBoxField` | no |
| `Signature` | `PdfFormSignatureField` | no |
| `PushButton` | `PdfFormPushButtonField` | no |
| `Unknown` | `PdfFormUnknownField` | no |

ALWAYS use the struct name `PdfFormTextField` for text fields. The name
`PdfFormFieldText` does NOT exist and is a compile error.

## Decision tree

### Reading vs filling

```
Need only to READ values, by name?
  -> form.field_values(document.pages())  (one HashMap, fastest path)

Need to READ values with type or geometry detail?
  -> iterate annotations(), narrow with as_form_field() + as_<kind>_field()

Need to FILL fields?
  -> iterate annotations() with `mut`-bound items
  -> as_form_field_mut() -> as_text_field_mut / as_checkbox_field_mut /
     as_radio_button_field_mut
  -> save_to_file() / save_to_bytes()
```

### Which field type

```
field.field_type() == PdfFormFieldType::Text       -> set_value(&str)
field.field_type() == PdfFormFieldType::Checkbox    -> set_checked(true | false)
field.field_type() == PdfFormFieldType::RadioButton -> set_checked()  (no arg)
ComboBox / ListBox / Signature / PushButton / Unknown
  -> readable only through the enum; no mutable narrowing
```

## Patterns

Condensed patterns below. Complete runnable code is in `references/examples.md`.

### Pattern 1: Detect a form

```rust
let pdfium = Pdfium::default();
let document = pdfium.load_pdf_from_file("form.pdf", None)?;
match document.form() {
    Some(form) => println!("form type {:#?}", form.form_type()),
    None => println!("no embedded form"),
}
```

`form()` returning `None` means the PDF carries no AcroForm or XFA form. It
NEVER means the document failed to load. To test for a form, use
`document.form().is_some()`.

### Pattern 2: Read all values in one call

```rust
if let Some(form) = document.form() {
    for (name, value) in form.field_values(document.pages()) {
        println!("{name} = {value:?}");
    }
}
```

`field_values` returns `HashMap<String, Option<String>>`. It is read-only.

### Pattern 3: Inspect fields by iterating annotations

```rust
for page in document.pages().iter() {
    for annotation in page.annotations().iter() {
        let Some(field) = annotation.as_form_field() else { continue };
        println!("{:?} type {:?}", field.name(), field.field_type());
    }
}
```

`as_form_field()` is the filter: it returns `None` for every annotation that is
not a `Widget` / `XfaWidget`. Treat `None` as `continue`, NEVER as an error.

### Pattern 4: Fill a text field

```rust
for page in document.pages().iter() {
    for mut annotation in page.annotations().iter() {
        if let Some(field) = annotation.as_form_field_mut() {
            if let Some(text) = field.as_text_field_mut() {
                text.set_value("Jane Doe")?;
            }
        }
    }
}
document.save_to_file("filled.pdf")?;
```

The iterated annotation MUST be bound `mut` (`for mut annotation in ...`) so
`as_form_field_mut()` is callable. `annotations().iter()` yields OWNED
`PdfPageAnnotation` values; there is no `iter_mut()`. `set_value` was added in
0.8.21.

### Pattern 5: Checkbox vs radio button

```rust
if let Some(checkbox) = field.as_checkbox_field_mut() {
    checkbox.set_checked(true)?;   // checkbox: takes a bool
} else if let Some(radio) = field.as_radio_button_field_mut() {
    radio.set_checked()?;          // radio: takes NO argument
}
```

`PdfFormCheckboxField::set_checked` takes a `bool`. `PdfFormRadioButtonField::set_checked`
takes NO argument, because selecting a radio button always means "on". Clear a
radio selection by selecting a different button in the same group.

### Pattern 6: Render the filled form

```rust
let config = PdfRenderConfig::new()
    .set_target_width(1600)
    .render_form_data(true);   // OFF by default
let bitmap = page.render_with_config(&config)?;
```

`render_form_data` defaults to `false`. A render WITHOUT it shows page content
but NOT filled field values. See `pdfium-syntax-rendering` for the full builder.

### Pattern 7: Save the filled document

```rust
document.save_to_file("filled.pdf")?;       // or
let bytes = document.save_to_bytes()?;       // -> Vec<u8>
```

See `pdfium-impl-saving` for `save_to_writer` and saving options.

## Version traps

| Item | 0.8.x | 0.9.x |
|------|-------|-------|
| `PdfFormField` enum + the eight field structs | added 0.7.34 | present |
| `PdfFormTextField::set_value` | added 0.8.21; absent before | present |
| Annotation mutability and creation | added 0.8.20 | present |
| `flatten()` reload behavior (affects forms) | changed 0.8.19 (#140) | present |
| Checkbox / radio `set_checked` | recent; tracked in issue #132 | present |
| All `FPDF_*` raw bindings, lifetime simplification, `Send` + `Sync` | safe bindings | `unsafe` bindings, simplified lifetimes, 0.9.0 |

ALWAYS target 0.8.21 or newer for text-field filling, and 0.8.19 or newer when
flattening a document that has form fields. Verify checkbox and radio
`set_checked` against the exact version you pin.

## Common mistakes

| Mistake | Correct approach |
|---------|------------------|
| `PdfFormFieldText` | `PdfFormTextField` (does not exist as `...FieldText`) |
| `radio.set_checked(true)` | `radio.set_checked()` (no argument) |
| `page.annotations().iter_mut()` | `for mut annotation in page.annotations().iter()` |
| `document.form()?.fields()` | `form.field_values(...)` or iterate annotations |
| `as_form_field()` is `None` so "no fields" | `None` means "not a Widget annotation" |
| Render without `render_form_data(true)` | filled values are invisible without it |
| Fill looks right, assume Acrobat is fine | verify in Acrobat (#145 appearance streams) |

Each mistake is explained with root cause and fix in
`references/anti-patterns.md`.

## Reference files

- `references/methods.md`: complete API signatures with version annotations.
- `references/examples.md`: working, verified Rust examples.
- `references/anti-patterns.md`: real failures, why they fail, and the fix.

## Related skills

- `pdfium-impl-annotations`: the annotation layer that wraps every form field.
- `pdfium-syntax-rendering`: the `PdfRenderConfig` builder and `render_with_config`.
- `pdfium-impl-saving`: persisting a filled document.
- `pdfium-impl-page-manipulation`: `flatten()` and its effect on form fields.
- `pdfium-errors-runtime`: handling `PdfiumError` from fill and save calls.

