| name | pdfium-impl-form-fields |
| description | 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
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires pdfium-render 0.8,0.9. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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
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
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
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
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
if let Some(checkbox) = field.as_checkbox_field_mut() {
checkbox.set_checked(true)?;
} else if let Some(radio) = field.as_radio_button_field_mut() {
radio.set_checked()?;
}
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
let config = PdfRenderConfig::new()
.set_target_width(1600)
.render_form_data(true);
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
document.save_to_file("filled.pdf")?;
let bytes = document.save_to_bytes()?;
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.