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:
- Generate — place interactive form fields in the layout tree with
Document.Create - Read — inspect existing form fields with
PdfiumDocument.GetFormFields() - Fill — set field values programmatically with
SetFormFieldValue() - 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
| Parameter | Type | Default | Description |
|---|---|---|---|
name | string | — | Field name (must be unique within the document). Used to address the field in GetFormFields() and SetFormFieldValue(). |
defaultValue | string | "" | Initial text value. Empty leaves the field unfilled. |
multiline | bool | false | Renders a scrolling text area instead of a single-line input. |
required | bool | false | Sets the Required flag — viewers may warn if the field is empty on submit. |
readOnly | bool | false | Displays the value but rejects user input. |
password | bool | false | Hides 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
| Parameter | Type | Default | Description |
|---|---|---|---|
name | string | — | Group name shared by all buttons in the same mutually-exclusive group. |
onStateName | string | "Yes" | Export value for this button. Must be unique within the group. |
required | bool | false | Sets the Required flag. |
readOnly | bool | false | Prevents 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
| Parameter | Type | Default | Description |
|---|---|---|---|
name | string | — | Field name (unique within the document). |
options | IReadOnlyList<string> | — | Option labels for the dropdown. |
defaultOption | string? | null | Pre-selected option. Must match an entry in options, or null for no selection. |
editable | bool | false | When true, users can type free-form text in addition to selecting from the list. |
required | bool | false | Sets the Required flag. |
readOnly | bool | false | Prevents 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
| Method | Description |
|---|---|
ButtonAction.Print | Opens the viewer's print dialog. |
ButtonAction.ResetForm | Resets 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
| Format | Description |
|---|---|
SubmitFormat.Fdf | Forms Data Format — PDF-native, compact. Default. |
SubmitFormat.Xfdf | XML Forms Data Format — XML-based, human-readable. |
SubmitFormat.Html | HTML form encoding (application/x-www-form-urlencoded) — works with standard web backends. |
SubmitFormat.Pdf | Sends 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
| Property | Type | Description |
|---|---|---|
Name | string | Fully qualified field name (/T entry). |
Type | PdfiumFormFieldType | Field type: TextField, CheckBox, RadioButton, ComboBox, ListBox, PushButton, Signature, or Unknown. |
Value | string? | Current value. "Yes"/"Off" for checkboxes. Null when no value is set. |
Flags | PdfiumFormFieldFlags | Raw 32-bit flag mask from the /Ff entry. |
PageIndex | int | Zero-based page index of the widget annotation. |
Rect | RectangleF | Bounding rectangle in PDF user-space points (origin at lower-left). |
Options | IReadOnlyList<string> | Option labels for combo/list box fields. Empty for other types. |
IsReadOnly | bool | Convenience: true when the ReadOnly flag is set. |
IsRequired | bool | Convenience: true when the Required flag is set. |
IsMultiline | bool | Convenience: true when the Multiline flag is set (text fields). |
IsPassword | bool | Convenience: 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");