Interactive Forms

Create PDFs with interactive AcroForm fields — text inputs, checkboxes, radio buttons, dropdowns, list boxes, and push buttons — using the same fluent layout API. Read, fill, and flatten form fields in existing PDFs.

Overview

FolioPDF supports the full AcroForm lifecycle:

  1. Generate — place interactive form fields in the layout tree with Document.Create
  2. Read — inspect existing form fields with PdfiumDocument.GetFormFields()
  3. Fill — set field values programmatically with SetFormFieldValue()
  4. Flatten — convert interactive widgets to static content with FlattenForm()

Form fields are positioned by the surrounding layout containers — the same Width, Height, Padding, and alignment modifiers you use for any other element control the field's size and position on the page.

Enabling Form Generation

Form creation is opt-in because it requires a post-processing round-trip through PDFium. Enable it in the document settings:

using FolioPDF;
using FolioPDF.Fluent;
using FolioPDF.Fluent.Form;
using FolioPDF.Helpers;
using FolioPDF.Infrastructure;

Document.Create(doc =>
{
    doc.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(40);
        page.Content().Column(col =>
        {
            col.Spacing(10);
            col.Item().Text("Application Form").FontSize(20).Bold();
            col.Item().Height(22).Width(300).TextField("fullName");
        });
    });
})
.WithSettings(new DocumentSettings { EnableForms = true })
.GeneratePdf("form.pdf");

Performance note: When EnableForms is true but the document contains no form elements, FolioPDF detects the empty registry and skips the PDFium round-trip entirely. There is no penalty for enabling the flag on documents that happen to have no fields.

Text Fields

Text fields accept user input. They can be single-line, multi-line, password-masked, required, or read-only.

col.Item().Height(22).Width(300).TextField("firstName");

// With a default value
col.Item().Height(22).Width(300).TextField("email", defaultValue: "user@example.com");

// Multi-line text area
col.Item().Height(80).Width(400).TextField("comments", multiline: true);

// Password field
col.Item().Height(22).Width(200).TextField("secret", password: true);

// Required + read-only
col.Item().Height(22).Width(300).TextField("accountId",
    defaultValue: "ACC-00042",
    readOnly: true,
    required: true);

TextField Parameters

ParameterTypeDefaultDescription
namestringField name (must be unique within the document). Used to address the field in GetFormFields() and SetFormFieldValue().
defaultValuestring""Initial text value. Empty leaves the field unfilled.
multilineboolfalseRenders a scrolling text area instead of a single-line input.
requiredboolfalseSets the Required flag — viewers may warn if the field is empty on submit.
readOnlyboolfalseDisplays the value but rejects user input.
passwordboolfalseHides typed characters in the viewer.

Checkboxes

Checkboxes are toggle fields with "Yes"/"Off" export values (the standard PDF convention).

// Basic checkbox
col.Item().Height(16).Width(16).CheckBox("agreeToTerms");

// Required checkbox
col.Item().Height(16).Width(16).CheckBox("dataConsent", required: true);

// Read-only pre-checked (set value after generation)
col.Item().Height(16).Width(16).CheckBox("verified", readOnly: true);

Initial state: Checkboxes start unchecked regardless of any initial-checked option — PDFium's button-value round-trip has a quirk that prevents reliable initial-state writing at creation time. To pre-check a checkbox, follow generation with SetFormFieldValue(name, true) on the loaded document.

Radio Buttons

Radio buttons are grouped by sharing the same name. Each button in the group must have a unique onStateName export value.

// Radio group: "priority"
col.Item().Row(row =>
{
    row.AutoItem().Height(16).Width(16).RadioButton("priority", onStateName: "Low");
    row.AutoItem().Padding(4, 0).Text("Low");

    row.AutoItem().Height(16).Width(16).RadioButton("priority", onStateName: "Medium");
    row.AutoItem().Padding(4, 0).Text("Medium");

    row.AutoItem().Height(16).Width(16).RadioButton("priority", onStateName: "High");
    row.AutoItem().Padding(4, 0).Text("High");
});

RadioButton Parameters

ParameterTypeDefaultDescription
namestringGroup name shared by all buttons in the same mutually-exclusive group.
onStateNamestring"Yes"Export value for this button. Must be unique within the group.
requiredboolfalseSets the Required flag.
readOnlyboolfalsePrevents user interaction.

Combo Boxes (Dropdowns)

Combo boxes present a dropdown list of options. They can optionally allow free-form text entry.

var countries = new[] { "United States", "Canada", "United Kingdom", "Germany", "Japan" };

// Basic dropdown
col.Item().Height(22).Width(250).ComboBox("country", countries);

// With default selection
col.Item().Height(22).Width(250).ComboBox("country", countries,
    defaultOption: "United States");

// Editable (user can type a custom value)
col.Item().Height(22).Width(250).ComboBox("department",
    new[] { "Engineering", "Sales", "Marketing", "HR" },
    editable: true);

ComboBox Parameters

ParameterTypeDefaultDescription
namestringField name (unique within the document).
optionsIReadOnlyList<string>Option labels for the dropdown.
defaultOptionstring?nullPre-selected option. Must match an entry in options, or null for no selection.
editableboolfalseWhen true, users can type free-form text in addition to selecting from the list.
requiredboolfalseSets the Required flag.
readOnlyboolfalsePrevents user interaction.

List Boxes

List boxes display a fixed-height scrollable list. Unlike combo boxes, the options are always visible.

var skills = new[] { "C#", "JavaScript", "Python", "Rust", "Go", "TypeScript" };

// Single selection
col.Item().Height(80).Width(200).ListBox("primarySkill", skills);

// Multi-select
col.Item().Height(100).Width(200).ListBox("allSkills", skills,
    multiSelect: true);

// With default
col.Item().Height(80).Width(200).ListBox("role",
    new[] { "Developer", "Designer", "Manager", "QA" },
    defaultOption: "Developer");

Push Buttons

Push buttons carry no form value — they trigger actions when clicked. Common actions include printing, resetting the form, submitting data to a URL, or executing JavaScript.

using FolioPDF.Toolkit.Pdfium;

// Print button
col.Item().Height(30).Width(100).PushButton("btnPrint",
    label: "Print",
    action: ButtonAction.Print);

// Reset form button
col.Item().Height(30).Width(120).PushButton("btnReset",
    label: "Reset Form",
    action: ButtonAction.ResetForm);

// Submit form to a URL
col.Item().Height(30).Width(100).PushButton("btnSubmit",
    label: "Submit",
    action: ButtonAction.SubmitForm("https://api.example.com/forms"));

// Submit with options (HTML encoding, POST)
col.Item().Height(30).Width(100).PushButton("btnSubmitHtml",
    label: "Submit",
    action: ButtonAction.SubmitForm(
        "https://api.example.com/forms",
        format: SubmitFormat.Html,
        method: SubmitMethod.Post,
        includeEmptyFields: true));

// Custom JavaScript
col.Item().Height(30).Width(120).PushButton("btnAlert",
    label: "Validate",
    action: ButtonAction.JavaScript("app.alert('Form is valid!');"));

ButtonAction Factory Methods

MethodDescription
ButtonAction.PrintOpens the viewer's print dialog.
ButtonAction.ResetFormResets all form fields to their default values.
ButtonAction.SubmitForm(url)Submits form data via HTTP POST in FDF format.
ButtonAction.SubmitForm(url, format, method, includeEmptyFields)Full control: FDF, XFDF, HTML, or PDF format; GET or POST; optional empty fields.
ButtonAction.JavaScript(script)Executes arbitrary JavaScript in the viewer.
ButtonAction.Named(name)Executes a named action (Print, NextPage, PrevPage, FirstPage, LastPage).

Submit Format Options

FormatDescription
SubmitFormat.FdfForms Data Format — PDF-native, compact. Default.
SubmitFormat.XfdfXML Forms Data Format — XML-based, human-readable.
SubmitFormat.HtmlHTML form encoding (application/x-www-form-urlencoded) — works with standard web backends.
SubmitFormat.PdfSends the entire filled PDF as the request body.

Reading Form Fields

Load an existing PDF and inspect its form fields:

using FolioPDF.Toolkit.Pdfium;

using var doc = PdfiumDocument.Load(File.ReadAllBytes("application.pdf"));

foreach (var field in doc.GetFormFields())
{
    Console.WriteLine($"Name: {field.Name}");
    Console.WriteLine($"Type: {field.Type}");
    Console.WriteLine($"Value: {field.Value}");
    Console.WriteLine($"Page: {field.PageIndex}");
    Console.WriteLine($"Rect: {field.Rect}");
    Console.WriteLine($"ReadOnly: {field.IsReadOnly}");
    Console.WriteLine($"Required: {field.IsRequired}");

    if (field.Options.Count > 0)
        Console.WriteLine($"Options: {string.Join(", ", field.Options)}");

    Console.WriteLine();
}

PdfiumFormField Properties

PropertyTypeDescription
NamestringFully qualified field name (/T entry).
TypePdfiumFormFieldTypeField type: TextField, CheckBox, RadioButton, ComboBox, ListBox, PushButton, Signature, or Unknown.
Valuestring?Current value. "Yes"/"Off" for checkboxes. Null when no value is set.
FlagsPdfiumFormFieldFlagsRaw 32-bit flag mask from the /Ff entry.
PageIndexintZero-based page index of the widget annotation.
RectRectangleFBounding rectangle in PDF user-space points (origin at lower-left).
OptionsIReadOnlyList<string>Option labels for combo/list box fields. Empty for other types.
IsReadOnlyboolConvenience: true when the ReadOnly flag is set.
IsRequiredboolConvenience: true when the Required flag is set.
IsMultilineboolConvenience: true when the Multiline flag is set (text fields).
IsPasswordboolConvenience: true when the Password flag is set (text fields).

Setting Form Values

Fill in fields programmatically using the PdfEditor fluent API or the PdfiumDocument directly:

Via PdfEditor (Fluent Chain)

using FolioPDF.Fluent;

PdfEditor.Open("application.pdf")
    .SetFormField("firstName", "Jane")
    .SetFormField("lastName", "Smith")
    .SetFormField("email", "jane@example.com")
    .SetFormField("agreeToTerms", true)
    .SetFormField("country", "United States")
    .Save("application-filled.pdf");

Via PdfiumDocument (Direct)

using var doc = PdfiumDocument.Load(File.ReadAllBytes("form.pdf"));

// Set text fields
doc.SetFormFieldValue("firstName", "Jane");
doc.SetFormFieldValue("lastName", "Smith");

// Set checkbox (true = checked, false = unchecked)
doc.SetFormFieldValue("agreeToTerms", true);

// Save the filled form
File.WriteAllBytes("form-filled.pdf", doc.SaveToBytes());

Flattening Forms

Flattening converts interactive fields into static content — the visual appearance is preserved but users can no longer edit the values. This is useful for archival or when distributing a filled form that should not be modified.

// Fluent chain: fill and flatten
PdfEditor.Open("application.pdf")
    .SetFormField("firstName", "Jane")
    .SetFormField("lastName", "Smith")
    .FlattenForm()
    .Save("application-final.pdf");

// Direct: flatten only
using var doc = PdfiumDocument.Load(File.ReadAllBytes("filled-form.pdf"));
doc.FlattenForm();
File.WriteAllBytes("flattened.pdf", doc.SaveToBytes());

Complete Example: Application Form

A full application form demonstrating every field type:

using FolioPDF;
using FolioPDF.Fluent;
using FolioPDF.Fluent.Form;
using FolioPDF.Helpers;
using FolioPDF.Infrastructure;
using FolioPDF.Toolkit.Pdfium;

Document.Create(doc =>
{
    doc.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(40);

        page.Content().Column(col =>
        {
            col.Spacing(8);

            // Header
            col.Item().Text("Employment Application").FontSize(22).Bold();
            col.Item().Text("Please fill in all required fields.").FontSize(10);
            col.Item().LineHorizontal(1);

            // Personal Information
            col.Item().PaddingTop(10).Text("Personal Information").FontSize(14).Bold();

            col.Item().Row(row =>
            {
                row.RelativeItem().Column(c =>
                {
                    c.Item().Text("First Name *").FontSize(9);
                    c.Item().Height(22).TextField("firstName", required: true);
                });
                row.ConstantItem(10);
                row.RelativeItem().Column(c =>
                {
                    c.Item().Text("Last Name *").FontSize(9);
                    c.Item().Height(22).TextField("lastName", required: true);
                });
            });

            col.Item().Text("Email *").FontSize(9);
            col.Item().Height(22).Width(300).TextField("email", required: true);

            // Department selection
            col.Item().PaddingTop(10).Text("Position").FontSize(14).Bold();

            col.Item().Text("Department").FontSize(9);
            col.Item().Height(22).Width(250).ComboBox("department",
                new[] { "Engineering", "Sales", "Marketing", "HR", "Finance" },
                editable: true);

            // Experience level (radio buttons)
            col.Item().Text("Experience Level").FontSize(9);
            col.Item().Row(row =>
            {
                row.AutoItem().Height(14).Width(14).RadioButton("experience", "Junior");
                row.AutoItem().PaddingHorizontal(4).Text("Junior").FontSize(9);
                row.AutoItem().Height(14).Width(14).RadioButton("experience", "Mid");
                row.AutoItem().PaddingHorizontal(4).Text("Mid-Level").FontSize(9);
                row.AutoItem().Height(14).Width(14).RadioButton("experience", "Senior");
                row.AutoItem().PaddingHorizontal(4).Text("Senior").FontSize(9);
            });

            // Skills (multi-select list)
            col.Item().PaddingTop(10).Text("Skills (select all that apply)").FontSize(9);
            col.Item().Height(80).Width(200).ListBox("skills",
                new[] { "C#", "JavaScript", "Python", "SQL", "Docker", "Kubernetes" },
                multiSelect: true);

            // Additional notes
            col.Item().PaddingTop(10).Text("Additional Notes").FontSize(9);
            col.Item().Height(80).TextField("notes", multiline: true);

            // Agreement
            col.Item().PaddingTop(10).Row(row =>
            {
                row.AutoItem().Height(14).Width(14).CheckBox("agreeToTerms", required: true);
                row.AutoItem().PaddingLeft(6).Text("I certify that the above information is correct.").FontSize(9);
            });

            // Buttons
            col.Item().PaddingTop(15).Row(row =>
            {
                row.AutoItem().Height(28).Width(80).PushButton("btnSubmit",
                    label: "Submit",
                    action: ButtonAction.SubmitForm("https://api.example.com/apply",
                        format: SubmitFormat.Html));
                row.ConstantItem(10);
                row.AutoItem().Height(28).Width(80).PushButton("btnReset",
                    label: "Reset",
                    action: ButtonAction.ResetForm);
                row.ConstantItem(10);
                row.AutoItem().Height(28).Width(80).PushButton("btnPrint",
                    label: "Print",
                    action: ButtonAction.Print);
            });
        });
    });
})
.WithSettings(new DocumentSettings { EnableForms = true })
.GeneratePdf("application-form.pdf");