How to Flatten PDF Annotations in JavaScript Using Dynamsoft Document Viewer
Burning (flattening) annotations means including them as vector graphics in the content of a PDF page, leaving them uneditable. Dynamsoft Document Viewer is a JavaScript SDK for document scanning and viewing, which can add annotations and export PDF files. In this article, we are going to explore how to use it.

What you’ll build: A browser-based JavaScript app that loads a PDF, lets users add annotations via an edit viewer, and exports a new PDF with all annotations flattened (burned) into the page content using Dynamsoft Document Viewer.
Key Takeaways
- Dynamsoft Document Viewer provides a
saveAnnotation: "flatten"option that merges annotations as vector graphics into the PDF page content, making them permanent and uneditable. - You can flatten all annotations at once or selectively flatten individual annotations by setting
annotation.flattened = truebefore saving withsaveAnnotation: "annotation". - The flattening process removes the
/Annotsarray from the page dictionary and appends the rendered annotation graphics directly to the page content stream. - This approach works entirely in the browser with no server-side processing required.
Common Developer Questions
- How do I flatten PDF annotations in JavaScript without a server?
- How do I burn annotations into a PDF page so they cannot be edited?
- What is the difference between
flatten,annotation,image, andnoneoptions when saving a PDF with Dynamsoft Document Viewer?
Prerequisites
- Basic knowledge of HTML and JavaScript
- A modern browser (Chrome, Edge, Firefox)
- Get a 30-day free trial license for Dynamsoft Document Viewer
Step 1: Set Up the Document Viewer with Annotation Support
-
Create a new HTML file with the following template.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Burn PDF Annotation</title> <style> </style> </head> <body> </body> <script> </script> </html> -
Include Dynamsoft Document Viewer’s files.
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.1.0/dist/ddv.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.1.0/dist/ddv.css"> -
Initialize Dynamsoft Document Viewer with a license. You can apply for one here.
Dynamsoft.DDV.Core.license = "LICENSE-KEY"; Dynamsoft.DDV.Core.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.1.0/dist/engine";// Lead to a folder containing the distributed WASM files await Dynamsoft.DDV.Core.init(); -
Create a new document instance.
const docManager = Dynamsoft.DDV.documentManager; const doc = docManager.createDocument(); -
Create an instance of edit viewer, bind it to a container and use it to view the document we just created. The buttons on it can be configured using a
UIConfigobject. Here, we add the annotation button to enable adding annotations.HTML:
<div id="viewer"></div>JavaScript:
Dynamsoft.DDV.setProcessingHandler("imageFilter", new Dynamsoft.DDV.ImageFilter()); let uiConfig = { type: "Layout", flexDirection: "column", className: "ddv-edit-viewer-desktop", children: [ { type: "Layout", className: "ddv-edit-viewer-header-desktop", children: [ { type: "Layout", children: [ "ThumbnailSwitch", "FitMode", "DisplayMode", "RotateLeft", "Crop", "Filter", "Undo", "Redo", "DeleteCurrent", "DeleteAll", "Pan", "SeparatorLine", "AnnotationSet" ], enableScroll: true }, { type: "Layout", children: [ { "type": "Pagination", "className": "ddv-edit-viewer-pagination-desktop" }, { type: Dynamsoft.DDV.Elements.Button, className: "ddv-button-download", events: { click: "exportPDFWithOptions", }, }, ] } ] }, "MainView" ] } editViewer = new Dynamsoft.DDV.EditViewer({ uiConfig: uiConfig, container: document.getElementById("viewer") });CSS:
#viewer { width: 320px; height: 480px; } -
Use
inputto select an image or PDF file and load it into the document instance.HTML:
<label> Select a file to load: <br/> <input type="file" id="files" name="files" onchange="filesSelected()"/> </label>JavaScript:
async function filesSelected(){ let filesInput = document.getElementById("files"); let files = filesInput.files; if (files.length>0) { const file = files[0]; const blob = await readFileAsBlob(file); await doc.loadSource(blob); // load the file } } function readFileAsBlob(file){ return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = async function(e){ const response = await fetch(e.target.result); const blob = await response.blob(); resolve(blob); }; fileReader.onerror = function () { reject('oops, something went wrong.'); }; fileReader.readAsDataURL(file); }) }
We’ll be able to see a viewer like the following:

Step 2: Flatten (Burn) the Annotations and Export PDF
Dynamsoft Document Viewer supports four ways to handle the PDF annotations:
none: Discard all the annotationsimage: Merge all the content into a raster imageflatten: Flatten all the annotationsannotation: Save the annotations in an editable form. Individual annotations marked as flattened will still be flattened
We can save the PDF file and burn the annotations using the flatten option.
let blob = await doc.saveToPdf({
saveAnnotation: "flatten"
})
If we want to keep some annotations editable while making some annotations flattened, we can use the flatten attribute of annotations and save the PDF using the annotation option.
let annotations = Dynamsoft.DDV.annotationManager.getAnnotationsByDoc(doc.uid);
let annotation = annotations[0];
annotation.flattened = true;
let blob = await doc.saveToPdf({
saveAnnotation: "annotation"
})
How PDF Annotation Flattening Works Internally
PDF files are described using PostScript. We are going to use some examples to show how the flattening works internally.
A PDF file contains a list of dictionaries and here is an example of a page dictionary:
4 0 obj
<<
/Type/Page % Specifies that this dictionary defines a page.
/Annots[ 8 0 R ] % A list of references to annotation objects on this page.
/Contents 7 0 R % Reference to page content stream.
/MediaBox[ 0 0 147 143.25] % Page dimensions.
% Other page properties
>>
endobj
7 0 obj
<</Filter/FlateDecode/Length 44>>stream
x??41W0 BCc=#S039椝 ?J缫w媹0Tp蒞? 卵
endstream
endobj
The above page has a reference to the following annotation dictionary:
8 0 obj
<<
/Type/Annot
/AP<<
/Contents(annotation)
/CreationDate(D:20241227135119+08'00')
/DA(0.9411764705882353 0.07450980392156863 0.0784313725490196 rg /Helvetica 16 Tf)
/DS(font: 'Helvetica' 16pt; text-align:left; color:#F01314)
/F 4
/IT/FreeTextTypeWriter/M(D:20241227135125+08'00')/NM(m56c4eb9uq)/RC(<?xml version="1.0"?><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:18.11.0" xfa:spec="2.0.2" style="font-size:16pt;text-align:left;color:#f01314;font-weight:normal;font-style:normal;font-family:'Helvetica';font-stretch:normal;"><p dir="ltr"><span>annotation</span></p></body>)
/Rect[ 22.1854 112.467 98.423 129.217]
/Subj()
/Subtype
/FreeText
/T()
>>
endobj
After flattening, the page dictionary will become the following. It no longer has the annotation node and a node of the converted annotation graphics is appended to its content.
4 0 obj
<<
/Type/Page
/Contents 13 0 R
/MediaBox[ 0 0 147 143.25]
>>
endobj
7 0 obj
<</Filter/FlateDecode/Length 48>>stream
x???41W0 BCc=#S039椝 ?J缫w媹0Tp蒞溻
靔 ?
endstream
endobj
13 0 obj
[ 7 0 R 14 0 R ]
endobj
14 0 obj
<</Filter/FlateDecode/Length 29>>stream
x?T0T0 B櫆珷镦b犩挴 M+?
endstream
endobj
Common Issues & Edge Cases
- Annotations not visible after flattening: If you saved with
saveAnnotation: "none", all annotations are discarded rather than flattened. Use"flatten"to burn them into the page, or"annotation"combined withflattened = trueon individual annotations. - Selectively flattened annotations appearing editable after reload: When using
saveAnnotation: "annotation", only annotations withflattened = trueare burned in. Re-opening the PDF in a viewer that supports PDF annotations will still show the remaining editable annotations — this is expected behavior. - Large PDFs causing slow export: The
saveToPdfcall is asynchronous; alwaysawaitit and consider showing a loading indicator for multi-page documents with many annotations.
Source Code
Get the source code of the demo to have a try: