Recursive AI

Fill and flatten form fields

All onepdf recipes

List a PDF form’s fields, fill text fields and check boxes in C#, save the filled form, then flatten it so the values become part of the page, with OnePDF.

C#
using OnePdf;
 
using (PdfDocument doc = PdfDocument.Open("of345.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    PdfAcroForm form = doc.Form ?? throw new InvalidOperationException("The file has no form.");
    Console.WriteLine($"{form.TerminalFields.Count()} fields, XFA: {form.HasXfa}");
 
    // Field names, kinds and allowed values.
    foreach (PdfFormField field in form.TerminalFields.Take(8))
    {
        string options = field.Options.Count > 0 ? $" options: {string.Join(", ", field.Options.Select(o => o.Export))}" : "";
        Console.WriteLine($"  {field.FullName} ({field.Kind}) = {field.Value}{options}");
    }
 
    try
    {
        form.SetValue("name", "Ada Lovelace");
        form.SetValue("dob", "12/10/1985");
        form.SetChecked("xno1", true);
        form.SetValue("missing", "x");                     // no such field
    }
    catch (PdfFormValueException e)
    {
        Console.WriteLine($"refused: {e.Reason}");         // nothing was changed by the refused call
    }
 
    doc.Save("filled.pdf");                                // still a fillable form
}
 
// Bake the values into the page and drop the form.
using (PdfDocument doc = PdfDocument.Open("filled.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    PdfFlattenResult result = doc.Flatten();
    Console.WriteLine($"form removed: {result.FormRemoved}");
    doc.Save("flat.pdf");
}
 
using (PdfDocument flat = PdfDocument.Open("flat.pdf"))
    Console.WriteLine($"form after flattening: {(flat.Form is null ? "none" : "still there")}; text has the name: {flat.Pages[0].ExtractText().Text.Contains("Ada Lovelace")}");
Text
53 fields, XFA: False
  name (Text) = 
  dob (Text) = 
  title (Text) = 
  homeaddr (Text) = 
  agcy (Text) = 
  xyes1 (CheckBox) = 
  xno1 (CheckBox) = 
  xyes2 (CheckBox) = 
refused: UnknownField
form removed: True
form after flattening: none; text has the name: True

The form is OF 345, a US government form. The values set before the refused call are kept: a call that fails changes nothing, but it doesn’t undo earlier calls.

Filling

  • Fields are found by fully qualified name (parent.child). TerminalFields lists the fields that hold values.
  • SetValue fills text fields, combo boxes and list boxes (by export value or display text) and radio groups (by state name or option text). SetValues fills a multi-select list box. SetChecked sets a check box.
  • A value that can’t be stored throws PdfFormValueException, whose Reason says why: UnknownField, WrongType, ReadOnly, NotInOptions, TooLong, NotMultiSelect or NotTerminal. PdfFillOptions can override read-only fields or cut text to the field’s maximum length.
  • OnePDF draws each filled field’s appearance and stores it, so the value shows in every viewer.
  • Field scripts never run. Calculated fields aren’t recomputed and format scripts aren’t applied: values are stored exactly as given. XFA form data isn’t filled.

Flattening

Flatten() draws each field’s appearance into the page and removes the fields; when none are left, the form itself goes (FormRemoved). The values are then ordinary page content, so text extraction finds them and nobody can edit them in a viewer.

  • PdfFlattenOptions.FieldNames flattens only the named fields. IncludeMarkup also flattens comments and other markup annotations.
  • Signed signature fields are kept.
  • Flattening works on encrypted documents, and new objects are encrypted like the rest.

Every rule is in Forms, and how appearances are drawn is in Appearance generation.

Longer walk-throughs on real government forms: Fill PDF form fields in C# and Flatten a PDF form in C#.