Flatten a PDF form in C#
Load the form with PdfFormManager, fill the fields, set Flatten to true and save: the values become ordinary page content and the form fields are removed, so the result can no longer be edited in a PDF viewer.
Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
2Create a console app
Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace
Program.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Create a fillable form from HTML
// 1. Create a fillable form: CreateFormFields turns the HTML controls into PDF form fields. HtmlToPdf converter = new HtmlToPdf(); converter.Options.CreateFormFields = true; PdfDocument doc = converter.ConvertHtmlString(""" <h1>Membership application</h1> <form> <p>Name: <input name="name" style="width:300px"></p> <p>Email: <input type="email" name="email" style="width:300px"></p> <p>Plan: <select name="plan"> <option>Basic</option><option>Premium</option></select></p> <p><input type="checkbox" name="terms"> I accept the terms</p> </form> """);Options.CreateFormFields = trueturns each HTML control into a PDF form field named after the control: two text boxes, a combo box and a check box. In your application the form is usually a PDF you already have; the last variation below loads one from a file. -
4Fill the fields
// 2. Fill the fields by name. PdfFormManager form = new PdfFormManager(); form.Load(doc); ((PdfFormFieldTextBox)form.Fields["name"]).Text = "Jane Doe"; ((PdfFormFieldTextBox)form.Fields["email"]).Text = "jane.doe@example.com"; ((PdfFormFieldComboBox)form.Fields["plan"]).SelectedValue = "Premium"; ((PdfFormFieldCheckBox)form.Fields["terms"]).Checked = true;
PdfFormManager.Loadaccepts aPdfDocument, a file, a stream or a byte array.Fields["name"]finds a field by name; cast it to its type to setText,SelectedValueorChecked. -
5Flatten and save
// 3. Flatten the whole form and write a new, compact file. form.Flatten = true; form.CompactSave = true; form.Save("application-flattened.pdf"); form.Close(); doc.Close();Flatten = trueturns every field into page content when the document is saved.CompactSave = truewrites a new file rather than appending the changes to the original, so the earlier, fillable version of the form is not kept inside the file. -
6Check the result
// 4. Check: the flattened file has no form fields left. PdfFormManager check = new PdfFormManager(); check.Load("application-flattened.pdf"); Console.WriteLine($"Form fields left: {check.Fields.Count}"); check.Close();Loading the flattened file again prints
Form fields left: 0: a flattened document has no form, andFieldsis an empty collection. The values are still on the page, as text. -
7Run it
dotnet run
The program writes
application-flattened.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
The complete Program.cs.
This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.
using SelectPdf.Universal;
// 1. Create a fillable form: CreateFormFields turns the HTML controls into PDF form fields.
HtmlToPdf converter = new HtmlToPdf();
converter.Options.CreateFormFields = true;
PdfDocument doc = converter.ConvertHtmlString("""
<h1>Membership application</h1>
<form>
<p>Name: <input name="name" style="width:300px"></p>
<p>Email: <input type="email" name="email" style="width:300px"></p>
<p>Plan: <select name="plan">
<option>Basic</option><option>Premium</option></select></p>
<p><input type="checkbox" name="terms"> I accept the terms</p>
</form>
""");
// 2. Fill the fields by name.
PdfFormManager form = new PdfFormManager();
form.Load(doc);
((PdfFormFieldTextBox)form.Fields["name"]).Text = "Jane Doe";
((PdfFormFieldTextBox)form.Fields["email"]).Text = "jane.doe@example.com";
((PdfFormFieldComboBox)form.Fields["plan"]).SelectedValue = "Premium";
((PdfFormFieldCheckBox)form.Fields["terms"]).Checked = true;
// 3. Flatten the whole form and write a new, compact file.
form.Flatten = true;
form.CompactSave = true;
form.Save("application-flattened.pdf");
form.Close();
doc.Close();
// 4. Check: the flattened file has no form fields left.
PdfFormManager check = new PdfFormManager();
check.Load("application-flattened.pdf");
Console.WriteLine($"Form fields left: {check.Fields.Count}");
check.Close();
Open the project on GitHub → this program and the variations below, ready to dotnet run
The PDF it produces.
Download application-flattened.pdf ↓ the file this sample produced, unedited
Variations you will need next.
Each variation is a separate file in the same project, compiled and run with the program above.
Flatten only some fields
Set Flatten on individual fields instead of on the whole form. Here the member's name and number are filled in and flattened, so they can no longer be changed, while the plan and the terms check box stay fillable for the member to complete.
using SelectPdf.Universal;
static class FlattenSomeFields
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
converter.Options.CreateFormFields = true;
PdfDocument doc = converter.ConvertHtmlString("""
<h1>Membership renewal</h1>
<form>
<p>Member: <input name="name" style="width:300px"></p>
<p>Member number: <input name="number" style="width:300px"></p>
<p>Plan: <select name="plan">
<option>Basic</option><option>Premium</option></select></p>
<p><input type="checkbox" name="terms"> I accept the terms</p>
</form>
""");
PdfFormManager form = new PdfFormManager();
form.Load(doc);
// fill in what you already know and flatten only those fields
PdfFormField name = form.Fields["name"];
((PdfFormFieldTextBox)name).Text = "Jane Doe";
name.Flatten = true;
PdfFormField number = form.Fields["number"];
((PdfFormFieldTextBox)number).Text = "M-10442";
number.Flatten = true;
// "plan" and "terms" stay fillable for the member
form.Save("renewal-prefilled.pdf");
form.Close();
doc.Close();
}
}
Flatten the form, then protect the file
GetDocument returns the flattened form as a PdfDocument, so security settings apply to it before it is saved. An owner password with CanEditContent = false turns off editing in viewers that honour the permissions, and setting a password is enough to encrypt the file with AES-256. Add a UserPassword as well if the content must stay confidential.
using SelectPdf.Universal;
static class FlattenAndProtect
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
converter.Options.CreateFormFields = true;
PdfDocument doc = converter.ConvertHtmlString("""
<h1>Expense claim</h1>
<form>
<p>Employee: <input name="employee" style="width:300px"></p>
<p>Amount: <input name="amount" style="width:150px"></p>
</form>
""");
PdfFormManager form = new PdfFormManager();
form.Load(doc);
((PdfFormFieldTextBox)form.Fields["employee"]).Text = "Jane Doe";
((PdfFormFieldTextBox)form.Fields["amount"]).Text = "412.50";
form.Flatten = true;
// GetDocument returns the flattened result as a PdfDocument, ready for security settings
PdfDocument flattened = form.GetDocument();
string ownerPassword = Environment.GetEnvironmentVariable("PDF_OWNER_PASSWORD") ?? "owner-change-me";
flattened.Security.OwnerPassword = ownerPassword;
flattened.Security.CanEditContent = false;
flattened.Save("expense-claim-protected.pdf");
flattened.Close();
form.Close();
doc.Close();
}
}
Fill and flatten an existing PDF file
Load takes the path of a fillable PDF you already have. Fill it, set Flatten and CompactSave, and save the result under a new name, which leaves the original form untouched for the next use.
using SelectPdf.Universal;
static class FlattenExistingFile
{
public static void Run()
{
// a fillable PDF that already exists (here made on the spot)
HtmlToPdf converter = new HtmlToPdf();
converter.Options.CreateFormFields = true;
PdfDocument blank = converter.ConvertHtmlString("""
<h1>Delivery note</h1>
<form>
<p>Received by: <input name="receiver" style="width:300px"></p>
<p><input type="checkbox" name="complete"> Delivery complete</p>
</form>
""");
blank.Save("delivery-note.pdf");
blank.Close();
// load the file, fill it and flatten it into a new file
PdfFormManager form = new PdfFormManager();
form.Load("delivery-note.pdf");
((PdfFormFieldTextBox)form.Fields["receiver"]).Text = "John Smith";
((PdfFormFieldCheckBox)form.Fields["complete"]).Checked = true;
form.Flatten = true;
form.CompactSave = true;
form.Save("delivery-note-final.pdf");
form.Close();
}
}
Common problems and fixes.
The PDF created from HTML has no form fields
Set converter.Options.CreateFormFields = true before converting; without it the controls are printed as they look and nothing can be filled in. The controls are read by a script, so JavaScriptEnabled must stay on. Hidden, file, image and button inputs never become fields.
Fields["..."] does not find a field
A converted field takes the name of its control, or its id when the control has no name, and a name used twice gets a suffix. Enumerate form.Fields and print each field's Name to see the names in your document.
The form was digitally signed
CompactSave writes a new document, and a signature the document carried no longer verifies. Leave CompactSave off when a signature has to survive: the default incremental save keeps the signed bytes.
The values must not be editable, but the form must remain a form
Set ReadOnly on the fields, or on the whole PdfFormManager, instead of flattening. Read-only fields keep their values and stay form fields.
Good to know.
- By default the controls of an HTML form are printed as they look on the page. With CreateFormFields set to true, every form control becomes a fillable PDF form field placed over the control as it was printed. Source
- Each converted field takes the name of its control, or its id when the control has no name. Hidden, file, image and button inputs are not converted. Source
- PdfFormManager.Flatten flattens the whole form on save; PdfFormField.Flatten flattens a single field. Source
- After the form is flattened and saved, the document has no AcroForm, and Fields returns an empty collection rather than null. Source
- By default Save performs an incremental update: the loaded document is kept unchanged and the changes are appended after it. CompactSave writes a new document instead, with only what is still reachable. Source
- GetDocument returns the current form state as a new PdfDocument for further processing, such as adding pages, merging or applying security. Source
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.