Flatten a PDF in C#: Make Filled Forms Read-Only in .NET
Blog / PDF Processing
PDF Processing

Flatten a PDF in C#: Make Filled Forms Read-Only in .NET

Demetra Theodorou · Oct 21, 2026

Make a filled PDF form read-only from .NET: upload, flatten and download with three async calls, lock only some fields, or generate and lock a form from HTML. Tested code throughout.

A flattened PDF is one where the form has been baked into the page. Whatever was typed into the fields is still visible, but there are no fields left to type into, and software that inspects the file finds a plain document. That is what you want before a completed form is archived, printed, emailed or passed on for signature.

This article shows how to flatten a PDF in C# with the Flatten PDF API and the official .NET SDK. It covers a whole document, a chosen set of fields, a form your own code fills from HTML, the same call with plain HttpClient, and an ASP.NET Core endpoint. Every sample on this page was run against the API before publishing.

You do not need iTextSharp, PdfSharp or a licensed PDF library for this. The work happens on the PDFGate side, and your code only uploads, calls one method, and downloads the result.

Key takeaways

  • After flattening, a form is just a page. The typed values are drawn into it and the fields themselves no longer exist.
  • In .NET it is three async calls: UploadFileAsync, FlattenPdfAsync, GetFileAsync. No iTextSharp or PdfSharp needed.
  • FieldNames freezes a chosen subset and leaves every other field editable.
  • Skip the SDK if you like. A multipart POST from HttpClient flattens a file in a single round trip.

What flattening does

  • Each text field, checkbox, radio group, dropdown and multi-line field is painted onto the page with its current value.
  • The AcroForm dictionary is dropped from the file. Acrobat shows a normal document, and a form library such as pdf-lib reports no fields.
  • The result is stored as a new document with its own ID while the input is kept as it was, so the editable and locked versions can coexist.
  • Passing FieldNames limits the operation to those fields. Everything else remains fillable, which is how you freeze a signature or an account number and leave the rest open.

Flattening and password protection solve different problems. A protected PDF keeps its form behind the password. To get both, flatten first and run the Protect PDF API on the output.

Install the .NET SDK

PdfGate.net targets .NET Standard 2.0, so it works from .NET 6, 8 and 10 and from .NET Framework 4.6.1 onwards, with System.Text.Json as its only dependency.

1dotnet add package PdfGate.net

Keys are issued in the dashboard. A test_ key is routed to the sandbox by the SDK, a live_ key to production. The listings read PDFGATE_API_KEY from the environment so no key sits in source.

Flatten a PDF file

For a file you already have, the sequence is upload, flatten, download. The upload yields a document ID, flattening that ID yields a second document, and the download of the second document is a Stream you can write to disk or hand to an HTTP response.

1using PdfGate.net;2using PdfGate.net.Models;34var apiKey = Environment.GetEnvironmentVariable("PDFGATE_API_KEY")5    ?? throw new InvalidOperationException("Set PDFGATE_API_KEY");6using var client = new PdfGateClient(apiKey);78// 1. Upload the PDF you want to flatten9await using var input = File.OpenRead("form.pdf");10PdfGateDocumentResponse uploaded = await client.UploadFileAsync(11    new UploadFileRequest { Content = input });1213// 2. Flatten every form field14PdfGateDocumentResponse flattened = await client.FlattenPdfAsync(15    new FlattenPdfRequest { DocumentId = uploaded.Id });1617// 3. Download the flattened PDF18await using Stream pdf = await client.GetFileAsync(19    new GetFileRequest { DocumentId = flattened.Id });20await using var output = File.Create("flattened.pdf");21await pdf.CopyToAsync(output);2223Console.WriteLine($"Flattened {flattened.Id} ({flattened.Size} bytes)");
  • UploadFileAsync accepts any readable Stream, so a large file is not loaded into memory first.
  • FlattenPdfAsync needs only DocumentId. The returned record has the new ID, the byte size, and DerivedFrom pointing at the upload.
  • GetFileAsync returns a Stream. await using releases the connection when the copy ends.
  • PdfGateClient is IDisposable and wraps an HttpClient. Keep one per application rather than one per request.

Run against the two-page form from the fillable fields benchmark, this takes the field count from 12 to 0 while every value stays on the page.

Flatten only some fields

FieldNames narrows the operation to the fields you list. Those are painted in and removed, the rest of the form stays live. The listing freezes three fields and leaves nine editable.

1PdfGateDocumentResponse partial = await client.FlattenPdfAsync(2    new FlattenPdfRequest3    {4        DocumentId = uploaded.Id,5        FieldNames = ["full_name", "customer_id", "start_date"]6    });78await using Stream partialPdf = await client.GetFileAsync(9    new GetFileRequest { DocumentId = partial.Id });10await using var partialOutput = File.Create("partially-flattened.pdf");11await partialPdf.CopyToAsync(partialOutput);1213Console.WriteLine($"Partially flattened {partial.Id}");
  • Names are the PDF's own field names, normally the name attributes the form was generated from. ExtractPdfFormDataAsync returns them as JSON keys if you need to discover them.
  • An unknown name makes the request fail with a 422 listing every name that was not found, and no field is touched.
  • A signature field that was never signed has no appearance to paint, so it is simply removed.

Fill a form from HTML, then flatten

In a .NET application the form is often not a file someone sends you. It is an order confirmation or a contract your own code assembles. Then there is nothing to fill. Build the HTML with the values already in the inputs, set EnableFormFields so those inputs become PDF fields, and flatten the generated document to freeze it.

1const string html = """2    <h1>Order confirmation</h1>3    <label>Customer <input type="text" name="customer" value="Acme GmbH"></label>4    <label>Seats <input type="number" name="seats" value="25"></label>5    <label>Plan6      <select name="plan">7        <option>Starter</option>8        <option selected>Business</option>9      </select>10    </label>11    <label><input type="checkbox" name="accept_terms" checked> Terms accepted</label>12    """;1314PdfGateDocumentResponse generated = await client.GeneratePdfAsync(15    new GeneratePdfRequest { Html = html, EnableFormFields = true });1617PdfGateDocumentResponse locked = await client.FlattenPdfAsync(18    new FlattenPdfRequest { DocumentId = generated.Id });1920await using Stream lockedPdf = await client.GetFileAsync(21    new GetFileRequest { DocumentId = locked.Id });22await using var lockedOutput = File.Create("order-confirmation.pdf");23await lockedPdf.CopyToAsync(lockedOutput);2425Console.WriteLine($"Generated {generated.Id} and flattened it as {locked.Id}");

A value, a selected option and a checked box all survive as field values, and flattening turns them into fixed text and a ticked box. Leave the flatten call out when the document is meant to be completed by someone else first. Which inputs are supported, and how to style them, is in the fillable fields guide.

Without the SDK (HttpClient)

Everything above is POST /forms/flatten. Because that endpoint accepts the PDF itself as a multipart part, HttpClient can skip the upload step, send the file and read the flattened bytes straight out of the response.

1using System.Net.Http.Headers;23var apiKey = Environment.GetEnvironmentVariable("PDFGATE_API_KEY")4    ?? throw new InvalidOperationException("Set PDFGATE_API_KEY");56using var http = new HttpClient();7http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);89using var form = new MultipartFormDataContent();10var file = new StreamContent(File.OpenRead("form.pdf"));11file.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");12form.Add(file, "file", "form.pdf");1314// Optional: flatten only these fields15form.Add(new StringContent("[\"full_name\", \"email\"]"), "fieldNames");1617using HttpResponseMessage response = await http.PostAsync(18    "https://api.pdfgate.com/forms/flatten", form);1920if (!response.IsSuccessStatusCode)21{22    Console.WriteLine(await response.Content.ReadAsStringAsync());23    return;24}2526await using var output = File.Create("flattened-http.pdf");27await response.Content.CopyToAsync(output);28Console.WriteLine("Saved flattened-http.pdf");
  • Supply file or documentId, never both. With a document ID the request can be plain application/json.
  • fieldNames is optional and, in a multipart body, is a JSON array encoded as a string.
  • Success is 201 with the PDF as the response body. jsonResponse set to true returns the document record instead, and preSignedUrlExpiresIn adds a temporary download link to it.
  • A test_ key has to be sent to api-sandbox.pdfgate.com. The SDK handles that routing, HttpClient does not.

In an ASP.NET Core endpoint

In a web application you rarely want the PDF on disk. Register one PdfGateClient as a singleton and return the stream from GetFileAsync with Results.File. ASP.NET Core streams it to the browser and disposes it when the response ends.

1using PdfGate.net;2using PdfGate.net.Models;34var builder = WebApplication.CreateBuilder(args);5builder.Services.AddSingleton(new PdfGateClient(builder.Configuration["PdfGate:ApiKey"]!));67var app = builder.Build();89app.MapPost("/documents/{id}/flatten", async (string id, PdfGateClient client) =>10{11    PdfGateDocumentResponse flattened = await client.FlattenPdfAsync(12        new FlattenPdfRequest { DocumentId = id });1314    Stream pdf = await client.GetFileAsync(new GetFileRequest { DocumentId = flattened.Id });15    return Results.File(pdf, "application/pdf", "flattened.pdf");16});1718app.Run();

Store the key in configuration or user secrets as PdfGate:ApiKey. If your documents live in PDFGate already, the endpoint only needs the ID, and the flatten plus download round trip is usually under two seconds for a typical form.

Notes and troubleshooting

  • Empty fields after flattening. The operation paints each field's stored appearance. A tool that wrote values without appearance streams leaves nothing to paint. Fill forms with something that regenerates appearances, such as the free fill PDF form tool, or produce the PDF from HTML as above.
  • Signatures. Removing form fields invalidates a digital signature, so flatten before signing. Documents completed through the Digital Signature API come back already flattened.
  • Limits. Uploads can be up to 500 MB. A flatten call counts as one request against the plan, the upload itself does not.
  • Sandbox output is watermarked with a PDFGate mark and otherwise identical to production.
  • Pre-signed URLs are only issued when file storage is enabled on the account. Otherwise the request is refused with a 422.

The Flatten PDF API reference lists every parameter and the response shape. The C# SDK documentation covers the rest of the client, and the same walkthrough exists for Java and Python.

Frequently asked questions

Can I flatten a PDF in C# without a third-party library?

Yes. The PDFGate Flatten PDF endpoint accepts the PDF as a multipart upload and returns the flattened PDF in the response body, so a plain HttpClient request is enough. The .NET SDK wraps the same endpoint in three typed calls.

Does flattening keep the values that were typed into the form?

Yes. Each field's appearance is drawn onto the page before the field is removed, so the text, checkbox and dropdown values remain visible. A value that was stored without an appearance stream has nothing to draw, so fill forms with a tool that updates appearances or generate the PDF from HTML.

Can I flatten only some fields and keep the rest editable?

Yes. Pass the field names in FieldNames. Those fields become static content and the rest of the form stays interactive. If a name does not exist in the document the API returns a 422 that lists the missing names.

Does flattening work on .NET Framework?

The PdfGate.net package targets .NET Standard 2.0, so it runs on .NET Framework 4.6.1 and later as well as .NET 6, 8 and 10.

Flattening is one of five processing endpoints. The Flatten PDF API page has the rest of the picture.

This website uses cookies to offer a better browsing experience. By clicking on the "Accept" button you accept our Cookie policy.