A practical guide for developers using the PDFluent SDK to handle both AcroForm and XFA-based PDF forms.
Set text field values, check checkboxes, select radio buttons, and choose dropdown options programmatically in any AcroForm PDF.
use pdfluent::prelude::*;
fn main() -> pdfluent::Result<()> {
let mut doc = PdfDocument::open("application.pdf")?;
if doc.form_fields()?.is_empty() {
eprintln!("This PDF has no form fields.");
return Ok(());
}
let mut form = doc.form_mut();
form.set_text("first_name", "Jane")?
.set_text("last_name", "Smith")?
.set_text("email", "jane@example.com")?;
form.set_checkbox("agree_terms", true)?
.set_checkbox("newsletter", false)?
.set_radio("payment_method", "credit_card")?;
doc.save("filled-application.pdf")?;
Ok(())
}Complete program, compiled against the published crate: Code on GitHub →
The same code, typed and run on screen. Watch on YouTube
Open the PDF with a mutable binding. Use form_fields() to check whether the document has any form fields before trying to fill them.
use pdfluent::prelude::*;
let mut doc = PdfDocument::open("application.pdf")?;
if doc.form_fields()?.is_empty() {
eprintln!("This PDF has no form fields.");
return Ok(());
}Call form_mut() to get a PdfFormMut value that lets you write field values. The accessor is infallible — errors surface on the individual set_* calls when a field does not exist or has a different type.
let mut form = doc.form_mut();Use set_text() with the field name as it appears in the PDF. Field names are case-sensitive. The setters chain via Result<&mut Self>.
form.set_text("first_name", "Jane")?
.set_text("last_name", "Smith")?
.set_text("email", "jane@example.com")?;Pass true or false to set_checkbox(). For radio buttons, pass the export value of the option you want selected. PDFluent writes the field value (/V) correctly. Note: in 1.0 the widget appearance state (/AS) on kid annotations is not yet synced — viewers that honour /AS may show stale visual state until they rebuild appearance from /V.
form.set_checkbox("agree_terms", true)?
.set_checkbox("newsletter", false)?
// Radio button: pass the export value, not the display label
.set_radio("payment_method", "credit_card")?;Drop the form handle scope and call save() on the document. flatten_forms() is on the 1.0 surface but currently returns Error::MissingDependency — the flatten runtime is tracked for a 1.x MINOR.
doc.save("application_filled.pdf")?;List all form fields in a PDF and read their current values. Covers text fields, checkboxes, radio buttons, dropdowns, and list boxes.
use pdfluent::PdfDocument;
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("form.pdf")?;
for field in doc.form_fields()? {
println!("{} = {} ({:?})", field.name, field.value, field.field_type);
}
Ok(())
}Open the document and check that it contains an AcroForm.
use pdfluent::PdfDocument;
let doc = PdfDocument::open("application.pdf")?;
if doc.form_fields()?.is_empty() {
println!("No form fields.");
return Ok(());
}Call doc.form() to get an immutable PdfForm handle.
let fields = doc.form_fields()?;
println!("{} form field(s)", fields.len());doc.form_fields() returns a slice of FormField values. Each entry has a name, field_type, and value.
for field in doc.form_fields()? {
println!("name: {}", field.name);
println!("type: {:?}", field.field_type);
println!("value: {:?}", field.value);
println!();
}Use form.field("name") to get a single field. Returns None if the field is not found.
if let Some(f) = doc.form_fields()?.iter().find(|f| f.name == "email") {
println!("Email: {}", f.value);
}Use form.values_as_map() to get all field names and their string representations in one call. Useful for logging or serialising form data.
use std::collections::HashMap;
let values: HashMap<String, String> = doc
.form_fields()?
.into_iter()
.map(|f| (f.name, f.value))
.collect();
for (name, val) in &values {
println!("{} = {}", name, val);
}Convert interactive form fields into static page content. Flattening bakes the current field values into the page so they cannot be edited.
use pdfluent::PdfDocument;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut doc = PdfDocument::open("filled_form.pdf")?;
doc.flatten_form()?;
doc.save("flattened.pdf")?;
println!("Form fields removed, values baked into page content.");
Ok(())
}Open the document that has form fields with values already set. Flattening works on whatever values are currently in the fields.
use pdfluent::PdfDocument;
let mut doc = PdfDocument::open("filled_application.pdf")?;Call flatten_form() to convert every field into static page content. The AcroForm dictionary is removed from the document.
doc.flatten_form()?;To flatten a subset of fields while leaving others editable, use flatten_forms() with a list of field names.
doc.flatten_fields(&["first_name", "last_name", "date_signed"])?;After flattening, !doc.form_fields()?.is_empty() returns false and the page text includes the values that were in the fields.
assert!(doc.form_fields()?.is_empty());
let text = doc.page(1)?.text()?;
assert!(text.contains("Jane Smith"));Save to a new file to keep the original editable version intact.
doc.save("application_final.pdf")?;Write field values into the XFA datasets packet to populate a dynamic XFA form with application data.
use pdfluent::{PdfDocument, XfaFieldValue};
fn main() -> pdfluent::Result<()> {
let mut doc = PdfDocument::open("blank_form.pdf")?;
doc.set_xfa_field_value("form1.subform1.firstName", XfaFieldValue::Text("Alice"))?;
doc.set_xfa_field_value("form1.subform1.lastName", XfaFieldValue::Text("Dupont"))?;
doc.set_xfa_field_value("form1.subform1.dob", XfaFieldValue::Text("1985-06-15"))?;
doc.save("filled_form.pdf")?;
Ok(())
}Open the template form file for mutation. The XFA structure must already exist; PDFluent cannot create an XFA template from scratch.
let mut doc = PdfDocument::open("blank_form.pdf")?;
if !doc.has_xfa_form() {
eprintln!("this document carries no XFA form");
}xfa_form_model() parses the template and the saved form state into a flat list. Each XfaField carries both the dotted name and the fully-indexed SOM path, and set_xfa_field_value accepts either.
let model = doc.xfa_form_model()?;
for field in &model.fields {
println!(
"{} type={:?} value={:?} read_only={}",
field.name, field.field_type, field.value, field.read_only,
);
}Values are typed. XfaFieldValue::Text covers text, numeric, date/time and dropdown fields; Checkbox takes a bool; Radio takes the on-value of the member to select. Field paths are dot-separated and case-sensitive.
use pdfluent::XfaFieldValue;
doc.set_xfa_field_value("form1.personal.firstName", XfaFieldValue::Text("Alice"))?;
doc.set_xfa_field_value("form1.personal.newsletter", XfaFieldValue::Checkbox(true))?;
doc.set_xfa_field_value("form1.personal.title", XfaFieldValue::Radio("Ms"))?;Each call applies immediately and returns an XfaSetOutcome. persisted_to_datasets tells you whether the value survives a save and reopen -- it is false only for bind="none" fields, which live in the form state and not in the datasets packet.
use pdfluent::XfaFieldValue;
use std::collections::HashMap;
let mut values: HashMap<&str, &str> = HashMap::new();
values.insert("form1.address.street", "123 Main St");
values.insert("form1.address.city", "Amsterdam");
values.insert("form1.address.postcode", "1234AB");
for (path, value) in &values {
let outcome = doc.set_xfa_field_value(path, XfaFieldValue::Text(value))?;
if !outcome.persisted_to_datasets {
eprintln!("{path} was set but is not bound to the datasets packet");
}
}The modified datasets XML is written back into the PDF. Save to a new file to preserve the original blank template.
doc.save("filled_form.pdf")?;Read the XFA data packet from a dynamic XFA form and access field values as typed Rust data.
use pdfluent::PdfDocument;
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("form.pdf")?;
let xfa = doc.xfa().ok_or(pdfluent::Error::NoXfa)?;
let datasets = xfa.datasets()?;
// Read a field value by its fully-qualified name
let first_name = datasets.field_value("form1.subform1.firstName")?;
println!("First name: {}", first_name.as_str().unwrap_or(""));
Ok(())
}doc.has_xfa_form() returns true when the PDF carries an XFA form. doc.xfa_form_model()? then returns an XfaFormModel with the layout page count and the fields in document order; it returns Error::Unsupported when the document has no XFA form.
let doc = PdfDocument::open("form.pdf")?;
let xfa = doc.xfa().ok_or(pdfluent::Error::NoXfa)?;XFA forms store submitted data in the xfa:datasets XML packet. xfa.datasets() parses that XML into a queryable tree.
let datasets = xfa.datasets()?;Field names are dot-separated paths from the root node. The path mirrors the XFA form template hierarchy.
let val = datasets.field_value("form1.subform1.firstName")?;
match val {
pdfluent::xfa::FieldType::Str(s) => println!("string: {}", s),
pdfluent::xfa::FieldType::Date(d) => println!("date: {:?}", d),
pdfluent::xfa::FieldType::Num(n) => println!("number: {}", n),
pdfluent::xfa::FieldType::Empty => println!("(empty)"),
}Use datasets.fields() to get every leaf node in the data tree.
for field in datasets.fields() {
println!("{} = {:?}", field.path(), field.value());
}If you need the raw XML for custom processing, access the bytes directly.
let xml_bytes = datasets.to_xml_bytes()?;
std::fs::write("form_data.xml", &xml_bytes)?;