Recursive AI

Fill PDF form fields in C#

Fill PDF form fields in C# with OnePDF: read field names, set text, check boxes, radio buttons and lists, catch bad values, then flatten. MIT or Apache-2.0.

To fill PDF form fields in C#, open the form with AllowEditing, call SetValue or SetChecked on doc.Form with each field’s name, and save. OnePDF checks each value against the field (its type, its options, its maximum length) and refuses one that doesn’t fit, then draws the new value into the field’s appearance so it shows in any viewer. It is pure C# with no native binaries, licensed MIT OR Apache-2.0. This page reads a form’s field names, fills every kind of field, fills from your own data while collecting bad values, and ends with flattening. Every sample was compiled and run against OnePDF 1.11.0 on .NET 10.

Before you start

TerminalNuGet
dotnet new console -o PdfForms
cd PdfForms
dotnet add package RecursiveAI.OnePdf --version 1.11.0

OnePDF targets .NET 10 (net10.0). The samples fill OPM’s SF 2823, the life-insurance beneficiary form for US federal employees. It has text fields, check boxes, two radio groups, a combo box, signature fields and buttons, so every case below runs on one real file. Save it as sf2823.pdf next to the program. The names and numbers filled in are made up.

Read the form fields

Start by listing the fields: you need their exact names, and for buttons and lists, the values they accept.

Program.csOnePDF 1.11.0 · .NET 10
using OnePdf;
 
using PdfDocument doc = PdfDocument.Open("sf2823.pdf");
PdfAcroForm form = doc.Form ?? throw new InvalidOperationException("The file has no form.");
Console.WriteLine($"{form.TerminalFields.Count()} fields, XFA: {form.HasXfa}");
 
foreach (PdfFormField field in form.TerminalFields)
{
    // The values a field accepts: options for combo and list boxes, on-states for check boxes and radio buttons.
    IEnumerable<string> accepts = field.Kind switch
    {
        PdfFieldKind.ComboBox or PdfFieldKind.ListBox => field.Options.Select(o => o.Export),
        PdfFieldKind.CheckBox or PdfFieldKind.RadioButton => field.Widgets
            .SelectMany(w => w.GetDictionary("AP")?.GetDictionary("N")?.Keys ?? [])
            .Where(state => state != "Off")
            .Distinct(),
        _ => [],
    };
    string max = field.MaxLength is int n ? $" (max {n})" : "";
    Console.WriteLine($"{field.FullName,-22} {field.Kind,-12} {string.Join(" | ", accepts)}{max}");
}
OutputExcerpt
61 fields, XFA: False
Bene 1.0               Text
not assigned           CheckBox     Yes
Clear                  PushButton
Print copies           ComboBox     Part 1 - Original | Part 2 - Duplicate
Witness Signature 1    Signature
Name                   Text
DOB                    Text
SSN                    Text          (max 11)
Type of Insured        RadioButton  Employee | Retiree | Compensationer
I Am                   RadioButton  Insured | Assignee
  • TerminalFields lists the fields that hold values. Names are fully qualified (parent.child): Bene 1.0 is the child 0 of a field named Bene 1.
  • A check box or radio button has a name for each “on” state, stored as a key of its widgets’ normal appearance (/AP /N). That is the value to set: here Yes for the check boxes and Employee, Retiree or Compensationer for the radio group. Don’t guess true or 1.
  • HasXfa tells you whether the file also carries an XFA form. OnePDF fills AcroForm fields, so an XFA-only form can’t be filled.
  • The same loop reads a filled form: field.Value is the current value, and field.Values gives every selection of a multi-select list.

Fill text fields, check boxes, radio buttons and lists

Program.csOnePDF 1.11.0 · .NET 10
using OnePdf;
 
using (PdfDocument doc = PdfDocument.Open("sf2823.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    PdfAcroForm form = doc.Form!;
 
    form.SetValue("Name", "Lovelace, Ada");                 // text
    form.SetValue("DOB", "12/10/1985");
    form.SetValue("SSN", "123-45-6789");
    form.SetValue("Type of Insured", "Employee");           // radio group: one of its on-states
    form.SetValue("Bene 1.0", "Byron King");
    form.SetValue("Relationship 1.0", "Son");
    form.SetValue("amount 1.0", "100%");
    form.SetValue("Total", "100%");
    form.SetValue("I Am", "Insured");
    form.SetChecked("not assigned", true);                  // check box
    form.SetChecked("witnessed", true);
    form.SetChecked("no beneficiary", true);
    form.SetValue("Print copies", "Part 2 - Duplicate");    // combo box: export value or display text
 
    doc.Save("filled.pdf");                                 // still a fillable form
}
 
// Read the values back.
using (PdfDocument filled = PdfDocument.Open("filled.pdf"))
{
    foreach (string name in new[] { "Name", "Type of Insured", "not assigned", "Print copies" })
        Console.WriteLine($"{name} = {filled.Form!.FindField(name)!.Value}");
}
OutputConsole
Name = Lovelace, Ada
Type of Insured = Employee
not assigned = Yes
Print copies = Part 2 - Duplicate

Page 1 of the filled SF 2823: the name Lovelace, Ada, the date of birth 12/10/1985 and the Social Security number in section A with “an employee” selected, beneficiary Byron King, Son, 100% in section B with a total of 100%, “the Insured” selected and all three statements checked in section C, and “Part 2 - Duplicate” chosen in the box at the bottom

The page above is filled.pdf as a viewer draws it. OnePDF built and stored each field’s appearance, so the values show without the viewer redrawing the fields, and the file is still a form anyone can edit. To add the changes after the original bytes instead of rewriting the file, save with doc.SaveIncremental(stream).

What each kind of field accepts

Field kindCallValue
TextSetValue(name, text)Any text up to the field’s maximum length. null clears it. Comb, multi-line and password fields are drawn from the field’s flags.
Check boxSetChecked(name, true) or SetValue(name, state)An on-state such as Yes, or Off. SetChecked(true) picks the first widget’s on-state.
Radio groupSetValue(name, state)One of the group’s on-states. Off or SetChecked(name, false) clears the group.
Combo box, list boxSetValue(name, choice)An option’s export value or display text. An editable combo box takes any text.
Multi-select list boxSetValues(name, choices)Several options, stored in option order.
Push button, signatureNoneRefused with WrongType. Signing is its own operation.

The same calls exist on each field: form.FindField(name)?.SetValue(...). Reset and ResetAll return fields to their defaults.

Fill a form from your data and catch bad values

In a real program the values come from a database row or a request, and some won’t fit the form. Each call that can’t store its value throws PdfFormValueException and changes nothing, so you can collect the problems and decide what to do:

Program.csOnePDF 1.11.0 · .NET 10
using OnePdf;
 
// Values from your own data: a database row, a request body, a CSV line.
var values = new Dictionary<string, string>
{
    ["Name"] = "Hopper, Grace",
    ["DOB"] = "12/09/1986",
    ["SSN"] = "123-45-67890",            // 12 characters: the field allows 11
    ["Type of Insured"] = "Contractor",  // not one of the radio group's states
    ["not assigned"] = "Yes",            // a check box's on-state
    ["Middle name"] = "Brewster",        // no such field
};
 
using PdfDocument doc = PdfDocument.Open("sf2823.pdf", new PdfOpenOptions { AllowEditing = true });
PdfAcroForm form = doc.Form!;
 
var problems = new List<string>();
foreach ((string name, string value) in values)
{
    try
    {
        form.SetValue(name, value);
    }
    catch (PdfFormValueException e)
    {
        problems.Add($"{name}: {e.Reason}");   // the refused call changed nothing
    }
}
 
if (problems.Count > 0)
    Console.WriteLine(string.Join(Environment.NewLine, problems));
doc.Save("filled-from-data.pdf");
OutputConsole
SSN: TooLong
Type of Insured: NotInOptions
Middle name: UnknownField

The name, date of birth and check box were stored. The other three were refused, each for its own reason:

ReasonWhen
UnknownFieldNo field has that fully qualified name.
WrongTypeThe call doesn’t fit the field, such as a value for a push button or a signature.
ReadOnlyThe field is read-only. PdfFillOptions.OverrideReadOnly fills it anyway.
NotInOptionsThe value isn’t one of a list’s options or a button’s on-states.
TooLongThe text is longer than the field’s maximum length. PdfFillOptions.TruncateToMaxLength cuts it instead.
NotMultiSelectSeveral values for a field that isn’t a multi-select list box.
NotTerminalThe name is a parent field. Fill its children.

Keep the form fillable or flatten it

A filled form stays editable: anyone can change the values in a viewer. To make them permanent, flatten the document after filling, with doc.Flatten() before Save. The values become page content and the fields are removed. Flattening, partial flattening and leaving out the Print and Clear buttons are covered in flatten a PDF form in C#. To fill one copy per record and combine them into one file, see merge PDF files in C#, which also shows how OnePDF keeps two filled copies of the same form apart.

What OnePDF doesn’t do when filling

  • Scripts never run. Calculate, format, validate and keystroke scripts are left as data. A total field isn’t recomputed and a date isn’t reformatted: values are stored exactly as you give them, so format them yourself.
  • XFA data isn’t filled. Forms that are XFA only can’t be filled. On forms that carry both, OnePDF fills the AcroForm fields, and flattening removes the XFA along with the form.
  • No new fields. OnePDF fills the fields a form has. It doesn’t add fields to a PDF.

Every rule for each field type is in forms, and how appearances are drawn is in appearance generation.

The same with iText

iText fills fields through PdfAcroForm: GetField(name) returns the field and SetValue sets it. iText’s knowledge base makes the same point about check boxes: “You shouldn’t ‘guess’ for the possible values. You need to use a value that is stored in the PDF” (how to check a check box). Written from those pages and run with iText 9.8.0 (AGPL or commercial):

Program.csiText 9.8.0
using iText.Forms;
using iText.Kernel.Pdf;
 
using PdfDocument pdf = new PdfDocument(new PdfReader("sf2823.pdf"), new PdfWriter("filled-itext.pdf"));
PdfAcroForm form = PdfAcroForm.GetAcroForm(pdf, true);
 
form.GetField("Name").SetValue("Lovelace, Ada");
form.GetField("Type of Insured").SetValue("Employee");
form.GetField("not assigned").SetValue("Yes");
form.GetField("Print copies").SetValue("Part 2 - Duplicate");

It fills the same four fields. The file’s producer then ends with “modified using iText® Core 9.8.0 (AGPL version)”, which the AGPL edition requires: see iText license cost and AGPL terms. PDFsharp’s FAQ describes its AcroForm support as limited.

Where next