Flatten a PDF in C# with Spire.PDF — form fields, annotations and layers become fixed page content

Flattening a PDF converts interactive objects into fixed page content. A filled-in form field loses its input control and keeps its value as text, a reviewer's comment becomes part of the page appearance, and optional-content layers are removed by rebuilding each page from its currently visible content. This makes the document less dependent on the interactive features of individual PDF viewers, although the page content itself can still be edited in a PDF editor.

This guide flattens a PDF in C# with Spire.PDF for .NET: the whole form at once, selected fields only, every annotation on every page, and layers by rebuilding each page through SVG. It closes with a verification step, because a flattened file looks very much like the original — the difference is structural, not visual.

Quick Navigation

  1. Install Spire.PDF for .NET
  2. What Flattening a PDF Does
  3. Flatten the Entire PDF Form
  4. Flatten Selected PDF Form Fields
  5. Flatten PDF Annotations and Comments
  6. Flatten PDF Layers Through SVG
  7. Flatten a PDF Before Printing
  8. Verify the Flattened PDF
  9. Putting It Together
  10. FAQ
  11. Conclusion

Install Spire.PDF for .NET

Spire.PDF for .NET is available as a NuGet package. Install it using the .NET CLI:

dotnet add package Spire.PDF

These examples were built with Spire.PDF for .NET 12.9.18.

You can also download Spire.PDF for .NET and add it to your project manually.

Three namespaces matter for flattening:

Namespace What lives there
Spire.Pdf PdfDocument, PdfPageBase, FileFormat
Spire.Pdf.Widget, Spire.Pdf.Fields PdfFormWidget and PdfField — the form side
Spire.Pdf.Interactive.Annotations PdfAnnotation, PdfAnnotationCollection — the annotation side, including Flatten()

What Flattening a PDF Does

Flattening presses the appearance of an interactive object into the page content and removes the object. The goal is to preserve the visible appearance while removing the interactivity — which is why this guide counts the objects left in the saved file instead of comparing screenshots.

Each object type needs its own call:

Object Flattened with
Form fields (text boxes, check boxes, list boxes, combo boxes) PdfForm.IsFlatten for the whole form, PdfField.Flatten for one field
Annotations (comments, highlights, stamps, free text) PdfAnnotation.Flatten() on each annotation
Layers (optional content groups) PDF → SVG → PDF conversion, which rebuilds every page from what is visible

Flatten the Entire PDF Form

Set Form.IsFlatten to true and every field in the document is converted in one pass:

using Spire.Pdf;

PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("FillableForm.pdf");

// Every field and its current value becomes page content.
pdf.Form.IsFlatten = true;

pdf.SaveToFile("FlattenedForm.pdf");
pdf.Close();

On the registration form used here — five fields, three of them filled — the field count drops from 5 to 0. The values stay on the page as selectable text.

The registration form before and after flattening — the page looks the same, the five interactive fields are gone

Flatten Selected PDF Form Fields

Locking only the fields that are already final is the more common requirement: a completed section becomes read-only while the rest of the form stays fillable. Fields are reached through the FieldsWidget collection of a PdfFormWidget, and Flatten is a boolean property on each one:

using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;

PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("FillableForm.pdf");

PdfFormWidget formWidget = pdf.Form as PdfFormWidget;

foreach (PdfField field in formWidget.FieldsWidget)
{
    // Flatten the fields that are final, leave the rest fillable.
    if (field.Name == "full_name" || field.Name == "email" || field.Name == "department")
    {
        field.Flatten = true;
    }
}

pdf.SaveToFile("PartiallyFlattenedForm.pdf");
pdf.Close();

On the sample form this flattens three fields and leaves start_date and manager editable — the count goes from 5 to 2. Filter on the field names your form defines — read them from FieldsWidget so the comparison matches exactly.

The partially flattened form — three fields are page text, start_date and manager are still fillable

Flattening a field vs. removing a field. Flattening keeps a field's visible appearance — including the value typed into it — and removes only the interactivity. Deleting a form field removes the object and its appearance together. If deletion is the actual goal, see Remove/Delete Form Fields from PDF in C# instead.

Flatten PDF Annotations and Comments

Annotations are the objects reviewers leave on top of a page: text comments, highlights, stamps and free text boxes. In Spire.PDF for .NET they are flattened through the interactive annotation model: create a PdfAnnotationCollection for each page and call Flatten() on each annotation.

using Spire.Pdf;
using Spire.Pdf.Interactive.Annotations;

PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("ReviewedDocument.pdf");

foreach (PdfPageBase page in pdf.Pages)
{
    if (page.Annotations.Count == 0) continue;

    PdfAnnotationCollection annotations = PdfAnnotationCollection.Create(page);

    // Flatten() removes the annotation from the collection, so walk it backwards.
    for (int i = annotations.Count - 1; i >= 0; i--)
    {
        annotations[i].Flatten();
    }
}

pdf.SaveToFile("FlattenedAnnotations.pdf");
pdf.Close();

The reviewed contract page in this guide carries three annotations — a free text note, a rubber stamp and a yellow highlight over a clause. After the loop the annotation count goes from 3 to 0, and all three marks are still drawn on the page: the note keeps its box and colour, the stamp keeps its border, the highlight stays behind the text. Only the interactive objects are gone. The highlight survives because its quad points are read from the saved page rather than from a page still in memory.

The reviewed contract before and after flattening — the stamp, the note and the highlight are still on the page, the annotation objects are gone

Two details in that loop are worth noting. Walk the collection backwards: Flatten() removes each annotation as it is flattened, so a back-to-front loop processes every item exactly once. Flatten() is declared on Spire.Pdf.Interactive.Annotations.PdfAnnotation, the model the annotation collection is created from.

Flatten PDF Layers Through SVG

Optional-content layers are removed by rebuilding each page from its currently visible content: convert each page to SVG and build the PDF back from it. The round trip redraws every page, which drops the layer structure while keeping text as text and graphics as vectors — so the result still selects, searches and scales, unlike a page rendered to an image.

A document with layers converts one page at a time:

using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Spire.Pdf;

// Work in a fresh temp folder for each run.
string workDir = Path.Combine(Path.GetTempPath(),
    "flatten_svg_" + Guid.NewGuid().ToString("N").Substring(0, 8));
Directory.CreateDirectory(workDir);

try
{
    // Step 1 - save every page as its own SVG file. A multi-page document gives
    // page_1.svg, page_2.svg, ...; a single-page document gives page.svg.
    PdfDocument source = new PdfDocument();
    source.LoadFromFile("LayeredDocument.pdf");
    int pageCount = source.Pages.Count;
    source.SaveToFile(Path.Combine(workDir, "page.svg"), FileFormat.SVG);
    source.Close();

    // Sort the files by page number; a single-page document produces page.svg.
    string[] svgFiles = Directory.GetFiles(workDir, "*.svg")
        .OrderBy(f =>
        {
            Match m = Regex.Match(Path.GetFileNameWithoutExtension(f), @"\d+$");
            return m.Success ? int.Parse(m.Value) : 0;
        })
        .ToArray();

    // One SVG per page — the merge below uses that count.
    if (svgFiles.Length != pageCount)
    {
        throw new IOException(
            $"SVG conversion produced {svgFiles.Length} files for {pageCount} pages.");
    }

    // Step 2 - load each SVG back and save it as a one-page PDF.
    string[] pageFiles = new string[svgFiles.Length];
    for (int i = 0; i < svgFiles.Length; i++)
    {
        PdfDocument page = new PdfDocument();
        page.LoadFromSvg(svgFiles[i]);
        pageFiles[i] = Path.Combine(workDir, "page_" + (i + 1) + ".pdf");
        page.SaveToFile(pageFiles[i], FileFormat.PDF);
        page.Close();
    }

    // Step 3 - join the one-page PDFs into a single document, in page order.
    PdfDocument.MergeFiles(pageFiles, "FlattenedLayers.pdf");
}
finally
{
    Directory.Delete(workDir, true);
}

The file names depend on the page count. A two-page layered document saved to page.svg produces page_1.svg and page_2.svg, while a one-page document produces a single page.svg with no number. On the sample document the round trip keeps both pages in order at the original page size, keeps the text that was drawn on each layer, and the layer count goes from 4 to 0. The same workflow on a single-page document returns one page.

The layered document rebuilt through SVG — the same two pages and the same content, with no layer structure left

Text and vector graphics survive the round trip, so the rebuilt pages still select, search and scale like the original, and the content that was drawn on each layer stays on the page.

For the two conversion steps on their own, see Convert PDF to SVG in C#, VB.NET and Convert SVG to PDF in C#, VB.NET.

Flatten a PDF Before Printing

Flatten a PDF for printing when the file is going to a printer, a print shop or a viewer you do not control. Once the fields, annotations and layers are ordinary page content, what gets printed is decided by the file rather than by the reader's interactive features.

No separate code is needed. Use the form calls from the form sections when the document has fields, the annotation loop when it has comments, and the SVG workflow when layer visibility could change what lands on paper — then run the output through the checks below before you send it.

Verify the Flattened PDF

A flattened file looks like the original, so confirm the result by counting objects in the saved file rather than by looking at a render:

using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;

PdfDocument result = new PdfDocument();
result.LoadFromFile("FlattenedForm.pdf");

int fields = 0;
PdfFormWidget form = result.Form as PdfFormWidget;
if (form != null)
{
    foreach (PdfField field in form.FieldsWidget) fields++;
}

int annotations = 0;
foreach (PdfPageBase page in result.Pages)
{
    annotations += page.Annotations.Count;
}

Console.WriteLine($"pages={result.Pages.Count} fields={fields} "
                + $"annotations={annotations} layers={result.Layers.Count}");
result.Close();

Count fields through PdfFormWidget.FieldsWidget. On the sample files this reports:

File Pages Fields Annotations Layers
FillableForm.pdf (input) 1 5 0 0
FlattenedForm.pdf 1 0 0 0
PartiallyFlattenedForm.pdf 1 2 0 0
ReviewedDocument.pdf (input) 1 0 3 0
FlattenedAnnotations.pdf 1 0 0 0
LayeredDocument.pdf (input) 2 0 0 4
FlattenedLayers.pdf 2 0 0 0

Then check the visual result: open the file in a reader and confirm nothing can be clicked or toggled, compare page count and page size with the original, and select or extract text to confirm it is still real text.

Putting It Together

The snippets above solve one concern each. Here they are as a single method that flattens the form fields and the annotations of any document in one pass:

using Spire.Pdf;
using Spire.Pdf.Interactive.Annotations;

/// <summary>
/// Flatten the form fields and annotations of a PDF in one pass.
/// </summary>
public static void FlattenDocument(string inputFile, string outputFile)
{
    PdfDocument pdf = new PdfDocument();
    pdf.LoadFromFile(inputFile);

    // 1. Form fields: one flag covers every field in the document.
    if (pdf.Form != null)
    {
        pdf.Form.IsFlatten = true;
    }

    // 2. Annotations: Flatten() removes each one, so walk backwards. Skip pages
    //    that carry no annotations.
    foreach (PdfPageBase page in pdf.Pages)
    {
        if (page.Annotations.Count == 0) continue;

        PdfAnnotationCollection annotations = PdfAnnotationCollection.Create(page);
        for (int i = annotations.Count - 1; i >= 0; i--)
        {
            annotations[i].Flatten();
        }
    }

    pdf.SaveToFile(outputFile);
    pdf.Close();
}

The guard keeps the loop on pages that carry annotations, so the same method also works for documents that only have form fields. With it, the method takes the five-field form to 0 fields and the reviewed contract from 3 annotations to 0. Layered documents go through the SVG workflow from the previous section instead.

FAQ

What is the difference between flattening and removing form fields?

Flattening keeps the field's visible appearance, including any entered value, and removes only the interactivity. Removing a form field deletes the field object and its appearance together.

Can I flatten only some form fields?

Yes. Cast pdf.Form to PdfFormWidget, walk FieldsWidget, and set Flatten = true on the fields you name. The rest stay fillable.

Does flattening keep the text selectable and searchable?

Yes. A flattened text field keeps its value in the page's text layer, and the SVG round trip keeps the text it redraws, so the values stay selectable, copyable and searchable.

Is flattening reversible?

Flattening writes the fields and annotations into the page, so keep the source PDF if you may need to edit them later.

Do I need to flatten before printing?

Not always, but it removes the dependency on the reader. Flattening can make the output more consistent across PDF viewers, because the visible content no longer depends on interactive fields, annotations or layer visibility settings.

Conclusion

Flattening a PDF in C# is one call per object type:

  • pdf.Form.IsFlatten = true flattens every field in the document; PdfField.Flatten flattens the ones you name.
  • PdfAnnotationCollection.Create(page) plus Flatten() on each item, walked backwards, flattens every comment, highlight and stamp on the page.
  • Layers are flattened by saving each page as SVG, loading it back and merging the pages with PdfDocument.MergeFiles.

The page keeps its appearance either way, so verify by counting the objects left in the saved file — not by comparing screenshots.

If you need to test Spire.PDF for .NET in a real business environment, you can apply for a temporary license.

A picture in Excel is more than decoration. Put a logo or a product shot on a worksheet and give it a hyperlink, and the file stops being a plain description and becomes something a reader can click through. A picture is not a cell, though, and its hyperlink lives on the picture object, so both setting and reading one go through the worksheet's picture collection rather than its cell hyperlink collection. Spire.XLS for JavaScript does this in the browser on WebAssembly, handling input and output files through a virtual file system (VFS) with no backend service.

This article covers two core feature points:

For installation and project setup, see Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module has been initialised.


Set a hyperlink on a picture

Setting a hyperlink on a picture is what turns it into a clickable entry point: a reader who clicks the picture on the worksheet opens the address it points at. The link belongs to the picture itself rather than to a cell, so the picture object has to be taken from the worksheet's picture collection first, and the address handed to that object. The steps are:

  1. Load the font, the picture and the test data file into the VFS.
  2. Load the workbook with workbook.LoadFromFile.
  3. Insert the picture into the space left for it with sheet.Pictures.Add.
  4. Set the hyperlink on the picture with picture.SetHyperLink, passing true as the second argument.
  5. Save the workbook with workbook.SaveToFile.
  6. Dispose of the workbook with workbook.Dispose to free resources.

The complete code example below shows how to set a hyperlink on a picture in Excel in React:

function App() {
  const pictureHyperlink = async () => {
    // get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // check that the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // load the font, the picture and the test data file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}data/`);
    const inputFileName = 'PictureLink.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // take the first worksheet and drop the picture into the space left for it
    const sheet = workbook.Worksheets.get(0);
    const picture = sheet.Pictures.Add({ topRow: 10, leftColumn: 1, fileName: 'logo.png' });

    // set the hyperlink on the picture; the second argument being true means it points outside the workbook
    picture.SetHyperLink('https://www.e-iceblue.com/Misc/about-us.html', true);

    // save the workbook
    const outputFileName = 'PictureHyperlink.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, fileFormat: xlsModule.FileFormat.Version2010 });

    // dispose of the workbook to free resources
    workbook.Dispose();

    // read the result file from the VFS and start the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Hyperlinks to Pictures in Excel</h1>
      <button id="picture-hyperlink" onClick={pictureHyperlink}>Set a hyperlink on the picture</button>
    </div>
  );
}

export default App;

After running, the effect of setting a hyperlink on a picture:

Set a hyperlink on a picture in Excel


Read the hyperlink from a picture

Reading the hyperlink off a picture is how a finished workbook is checked over: whether the links are all there, and whether they point at the right addresses. The addresses that come back are written out as a text file, which makes them easy to line up against a list of links. Picture hyperlinks are not kept in the cell hyperlink collection, so the pictures have to be taken from the worksheet one by one, and each one asked for its own link object. The steps are:

  1. Load the font and the test data file into the VFS.
  2. Load the workbook with workbook.LoadFromFile.
  3. Take the picture count from sheet.Pictures.Count and walk every picture on the worksheet with it.
  4. Take this picture's hyperlink object with picture.GetHyperLink.
  5. Read the target address from the link object's Address, collecting the addresses as you go.
  6. Write the collected addresses into a text file with FS.writeFile, one address per line.
  7. Dispose of the workbook with workbook.Dispose to free resources.

The complete code example below shows how to read the hyperlink from a picture in Excel in React:

function App() {
  const readPictureHyperlink = async () => {
    // get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

    // check that the module is ready
    if (!xlsModule) {
      alert('Spire.Xls is not ready yet');
      return;
    }

    // load the font and the test data file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'PictureHyperlinks.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // walk the pictures of the worksheet and read the hyperlink off each one
    const sheet = workbook.Worksheets.get(0);
    const pictures = sheet.Pictures;
    const addresses = [];
    for (let i = 0; i < pictures.Count; i++) {
      const link = pictures.get(i).GetHyperLink();
      addresses.push(link.Address);
    }

    // write the addresses that were read into a text file
    const outputFileName = 'ReadPictureHyperlink.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, addresses.join('\n'));

    // dispose of the workbook to free resources
    workbook.Dispose();

    // read the result file from the VFS and start the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Hyperlinks to Pictures in Excel</h1>
      <button id="read-picture-hyperlink" onClick={readPictureHyperlink}>Read the hyperlink from the picture</button>
    </div>
  );
}

export default App;

After running, the effect of reading the hyperlink from a picture:

Read the hyperlink from a picture in Excel


FAQ

Why does a picture's hyperlink not show up in sheet.HyperLinks?

Cause: sheet.HyperLinks collects cell hyperlinks only. A picture is not a cell; it belongs to the worksheet's picture collection, and the link hangs on the picture object itself.

Solution: Go through the picture collection. In this example, after a hyperlink was set on the picture, sheet.HyperLinks.Count was still 0, and it was still 0 after the file was saved and opened again. Read a picture's link with sheet.Pictures.get(i).GetHyperLink(), then read the Address off the link object.

When should the second argument of SetHyperLink be false?

Cause: The second argument of picture.SetHyperLink(address, isExternal) says whether the target lies outside the workbook.

Solution: Pass true for a web page or an external file, and the link type comes out as HyperLinkType.Url; pass false for a position inside the same workbook, write the address as a cell reference such as Sheet1!A1, and the type comes out as HyperLinkType.Workbook. In this example both forms were saved, and after the file was opened again the Address and Type read back matched what had been set.

Get a Free License

Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

A PDF moving through a workflow carries markup from the previous review round: a highlighted point, a change request, a sticky note that says "to confirm". Passing those traces on as-is invites misreading, and can leak internal discussion along with them. Deleting them one by one in a reader does not scale once there are many documents.

This article uses Spire.PDF for JavaScript to delete annotations from a PDF document. It loads, modifies, and saves PDF documents in the browser with WebAssembly; everything runs locally, files are read and written through a virtual file system (VFS), and no backend is involved. Deletion works on the PdfPage.Annotations collection: remove one entry by index, or clear a whole page in a single call.

This article covers two core features:

For installation and project setup, see How to Integrate Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Delete a Specific Annotation

Spire.PDF for JavaScript exposes the PdfPage.Annotations collection for managing a page's annotations. RemoveAt() removes one entry by index, starting at 0. the collection is ordered, so once an entry is removed the indices of the annotations after it shift down by one.

function App() {
  const deleteSpecificAnnotation = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF with annotations into the VFS
    const inputFileName = 'DocumentWithAnnotations.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Delete the annotation at index 0 on page 1
    doc.Pages.get_Item(0).Annotations.RemoveAt(0);

    const outputFileName = 'DeleteSpecificAnnotation.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete a Specific Annotation</h1>
      <button onClick={deleteSpecificAnnotation} id="btn-1">
        Delete
      </button>
    </div>
  );
}

export default App;

The annotation at index 0 on page 1 is removed and the remaining annotations are unchanged

The annotation at index 0 on page 1 is removed and the remaining annotations are unchanged


Delete All Annotations from the Document

Spire.PDF for JavaScript also provides the PdfAnnotationCollection.Clear() method, which wipes every annotation on a page at once. It does not distinguish by type or count — the whole page is cleared together. To clear an entire document, walk doc.Pages and call it once per page.

function App() {
  const deleteAllAnnotations = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the PDF with annotations into the VFS
    const inputFileName = 'DocumentWithAnnotations.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Walk every page and clear all its annotations
    for (let i = 0; i < doc.Pages.Count; i++) {
      doc.Pages.get_Item(i).Annotations.Clear();
    }

    const outputFileName = 'DeleteAllAnnotations.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete All Annotations from the Document</h1>
      <button onClick={deleteAllAnnotations} id="btn-2">
        Clear
      </button>
    </div>
  );
}

export default App;

Annotations are cleared from every page and the body content is left untouched

Annotations are cleared from every page and the body content is left untouched


FAQ

The wrong annotation got deleted

Cause: RemoveAt(index) locates an entry by index, and a text annotation or free-text annotation often generates a popup child annotation automatically. Both count in the Annotations collection, so the actual index does not line up with the number of notes you see on the page.

Solution: Walk the collection first, check each entry's index and type, then decide which one to remove. To remove only the text annotation itself, skip the popup child:

const annotations = doc.Pages.get_Item(0).Annotations;
for (let i = 0; i < annotations.Count; i++) {
  // A popup annotation is a child derived from a text annotation; skip it
  if (annotations.get_Item(i) instanceof pdfModule.PdfPopupAnnotationWidget) continue;
  annotations.RemoveAt(i);
  break;
}

Forward loop deletion skips half the annotations

Cause: After RemoveAt() removes one entry, the indices of every annotation after it drop by one. Deleting with a forward loop for (let i = 0; i < annotations.Count; i++) skips the entry right after each removed one.

Solution: Delete in reverse order, or just call Clear() on the page:

// Delete in reverse to avoid skipping entries as indices shift down
const annotations = doc.Pages.get_Item(0).Annotations;
for (let i = annotations.Count - 1; i >= 0; i--) {
  annotations.RemoveAt(i);
}

Does Clear() wipe the body content too

Cause: Clear() operates on the current page's Annotations collection, and that collection holds annotation objects only — no body text, images, or form fields.

Solution: Clear() removes annotations only and leaves the page body as it is. To clear just some pages, call it per page instead of walking the whole document:

// Clear annotations on page 2 (index 1) only
doc.Pages.get_Item(1).Annotations.Clear();

Get a Free License

If you want to remove the evaluation message from the resulting documents, or to get rid of the functional limitations, please contact sales for a temporary license valid for 30 days.

Page 9 of 371
page 9