> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/get-started/samples/showcase-demo-xfdf-annotations-in-pdf.md).

# XFDF Annotations in PDF Showcase Demo Code Sample

Import and export PDF annotations with the XFDF format. This code relates to the XFDF annotation page in our showcase demo.

{% hint style="info" %}
**Requirements**

*These packages are required to use these features in production. Trial keys have unlimited access to all features*

<a href="/web/get-started/readme.md" class="button primary">Web SDK</a><a href="https://showcase.apryse.com/xfdf-annotations-in-pdf" class="button primary">Live demo</a>
{% endhint %}

Quickly import and export PDF annotations using XFDF — an XML-based format that captures details like position, color, and content.

This demo allows you to:

* Upload your own PDF file
* Edit the layout and content and obtain the XFDF
* Preview the XFDF with the view document XFDF option

**Implementation steps** To add XFDF import and export capability on PDFs with WebViewer:

Step 1: [Get started with WebViewer](/web/get-started/readme.md) in your preferred web stack Step 2: Add the ES6 JavaScript sample code provided in this guide

Once you generate your license key, it will automatically be included in your sample code below.

{% @apryse-license-key/apryse-license-key platform="WEB\_VIEWER" variant="compact" %}

<pre class="language-js" data-line-numbers><code class="lang-js">// ES6 Compliant Syntax
// GitHub Copilot v1.0, Claude 3.5 Sonnet, July 28, 2025
// File: index.js

import WebViewer from '@pdftron/webviewer';

// XFDF Annotations section
// 
// Code to customize the WebViewer to export and import XFDF strings 
// and annotations.
//

// Default document with annotations
const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/demo-annotated.pdf';

// XFDF string to be loaded as an annotation into the viewer
let xfdf =
    `&#x3C;?xml version="1.0" encoding="UTF-8" ?>
  &#x3C;xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
    &#x3C;pdf-info xmlns="http://www.pdftron.com/pdfinfo" version="2" import-version="4" />
    &#x3C;fields />
    &#x3C;annots />
    &#x3C;pages>
      &#x3C;defmtx matrix="1,0,0,-1,0,792" />
    &#x3C;/pages>
  &#x3C;/xfdf>`

const customizeUI = async (instance) => {
    // Load the default file
    await instance.Core.documentViewer.loadDocument(defaultDoc);
};

// Export annotations from the document as XFDF string
const extractXFDFFromDocument = async (instance) => {
    const xfdfString = await instance.Core.annotationManager.exportAnnotations();
    if (xfdfString) {
        xfdf = xfdfString;
    }
};

// Get the XFDF code from the code block element to insert into the viewer
const getCodeFromCodeBlock = async (instance) => {
    const code = document.querySelector('#xfdf-code')?.textContent;
    if (code) {
        await instance.Core.annotationManager.importAnnotations(code);
        instance.Core.documentViewer.refreshAll();
    }
};

// Format the XFDF string for better readability in the code block
const getFormattedXFDF = () => {
    let formatted = '';
    let indent = '';
    const tab = '  ';

    // Clean up the XFDF string by removing unnecessary whitespace
    const xml = xfdf.replace(/>\s+&#x3C;/g, '>&#x3C;').replace(/>&#x3C;/g, '>\n&#x3C;');

    // Format the cleaned XFDF string
    xml.split(/>\s*&#x3C;/).forEach((node) => {
        if (node.match(/^\/\w/)) {
            indent = indent.substring(tab.length);
        }
        formatted += indent + '&#x3C;' + node + '>\n';
        if (node.match(/^&#x3C;?\w[^>]*[^\/]$/)) {
            indent += tab;
        }
    });

    return formatted.substring(1, formatted.length - 2);
};


// WebViewer section
// 
// This code initializes the WebViewer with the basic settings
// that are found in the default showcase WebViewer
//

const searchParams = new URLSearchParams(window.location.search);
const history = window.history || window.parent.history || window.top.history;
const licenseKey = '<code class="expression">visitor.claims.wvKey || "YOUR_WEBVIEWER_LICENSE_KEY"</code>'; 
const element = document.getElementById('viewer');

// Initialize WebViewer with the specified settings
WebViewer({
    path: '/lib',
    licenseKey: licenseKey,
}, element).then((instance) => {
    // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
    const cloudyTools = [
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
        instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
    ];
    instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
    instance.UI.disableTools(cloudyTools);

    // Set default toolbar group to Annotate
    instance.UI.setToolbarGroup('toolbarGroup-Annotate');

    // Set default tool on mobile devices to Pan.
    // https://apryse.atlassian.net/browse/WVR-3134
    if (isMobileDevice()) {
        instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
    }

    instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
        if (searchParams.has('file')) {
            searchParams.delete('file');
            history.replaceState(null, '', '?' + searchParams.toString());
        }
    });

    instance.Core.annotationManager.enableAnnotationNumbering();

    instance.UI.NotesPanel.enableAttachmentPreview();

    // Add the demo-specific functionality
    customizeUI(instance).then(() => {
        // Create UI controls after demo is initialized
        createUIControls(instance);
    });
});

// Function to check if the user is on a mobile device
const isMobileDevice = () => {
    return (
        /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
            window.navigator.userAgent
        ) ||
        /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
            window.navigator.userAgent.substring(0, 4)
        )
    );
}

// Cleanup function for when the demo is closed or page is unloaded
const cleanup = (instance) => {
    if (typeof instance !== 'undefined' &#x26;&#x26; instance.UI) {
        // Clean up any resources if needed
        console.log('Cleaning up xfdf-annotations demo');
    }
};

// Register cleanup for page unload
window.addEventListener('beforeunload', () => cleanup());
window.addEventListener('unload', () => cleanup());

// UI section
//
// Helper code to add controls to the viewer holding the buttons
// This code creates a container for the buttons, styles them, and adds them to the viewer
//

// Choose File button
const filePicker = (instance) => {
    const button = document.createElement('button');
    button.className = 'btn-filepicker';
    button.textContent = 'Choose File';
    button.onclick = () => {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.pdf';
        input.onchange = async (event) => {
            const file = event.target.files[0];
            if (file) {
                const doc = await instance.Core.createDocument(file);
                instance.Core.documentViewer.loadDocument(doc);
            }
        };
        input.click();
    };

    return button;
}

// XFDF Modal for displaying and editing XFDF code
const xfdfModal = (instance) => {
    const modal = document.createElement('div');
    modal.className = 'xfdf-modal';

    // Close the modal when clicking outside of it
    modal.onclick = (event) => {
        if (event.target === modal) {
            modal.style.display = 'none';
        }
    };

    // Modal content (non-shaded part)
    const content = document.createElement('div');
    content.className = 'xfdf-modal-content';

    // header
    const header = document.createElement('div');
    header.className = 'xfdf-modal-header';

    // Title
    const title = document.createElement('h2');
    title.textContent = 'Form Data';

    // Close button
    const close = document.createElement('button');
    close.className = 'btn-xfdf-modal-close';
    close.textContent = '×';
    close.onclick = () => {
        modal.style.display = 'none';
    };

    // Assemble the modal header
    header.appendChild(title);
    header.appendChild(close);

    // Code block for XFDF
    const codePre = document.createElement('pre');
    codePre.className = 'xfdf-pre';
    const codeBlock = document.createElement('code');
    codeBlock.id = 'modal-xfdf-code';
    codeBlock.contentEditable = true;
    codeBlock.textContent = getFormattedXFDF();
    codeBlock.onchange = () => {
        xfdf = codeBlock.textContent;
    };
    codePre.appendChild(codeBlock);

    // Modal footer
    const footer = document.createElement('div');
    footer.className = 'xfdf-modal-footer';

    // Zoom buttons
    const zoom = document.createElement('div');
    zoom.className = 'xfdf-modal-zoom';

    // Zoom in button
    const zoomIn = document.createElement('button');
    zoomIn.className = 'btn-zoom-in';
    zoomIn.textContent = '+';
    zoomIn.onclick = () => {
        const codeBlock = document.querySelector('#modal-xfdf-code');
        if (codeBlock) {
            const currentFontSize = parseFloat(window.getComputedStyle(codeBlock).fontSize);
            codeBlock.style.fontSize = (currentFontSize + 2) + 'px';
        }
    };

    // Zoom label
    const zoomLabel = document.createElement('span');
    zoomLabel.textContent = 'Zoom';

    // Zoom out button
    const zoomOut = document.createElement('button');
    zoomOut.className = 'btn-zoom-out';
    zoomOut.textContent = '-';
    zoomOut.onclick = () => {
        const codeBlock = document.querySelector('#modal-xfdf-code');
        if (codeBlock) {
            const currentFontSize = parseFloat(window.getComputedStyle(codeBlock).fontSize);
            codeBlock.style.fontSize = (currentFontSize - 2) + 'px';
        }
    };

    // Assemble the zoom controls
    zoom.appendChild(zoomIn);
    zoom.appendChild(zoomLabel);
    zoom.appendChild(zoomOut);

    // Ok button to accept changes and close the modal
    const ok = document.createElement('button');
    ok.className = 'btn-xfdf-modal-ok';
    ok.textContent = 'OK';
    ok.onclick = async () => {
        xfdf = codeBlock.textContent;
        modal.style.display = 'none';
    };

    // Assemble the modal footer
    footer.appendChild(zoom);
    footer.appendChild(ok);

    // Append all parts to the modal content
    content.appendChild(header);
    content.appendChild(codePre);
    content.appendChild(footer);
    modal.appendChild(content);

    return modal;
};

// XFDF Code Block Element
const xfdfElement = () => {
    const wrapper = document.createElement('div');
    wrapper.className = 'xfdf-wrapper';

    // Open modal for XFDF code block
    const button = document.createElement('button');
    button.className = 'btn-modal';
    button.textContent = 'Open in Dialog';
    button.onclick = () => {
        const modal = document.querySelector('.xfdf-modal');
        if (!modal) {
            console.error('XFDF modal not found');
            return;
        }
        // Set the XFDF code block content
        const modalCodeBlock = modal.querySelector('#modal-xfdf-code');
        if (modalCodeBlock) {
            modalCodeBlock.textContent = getFormattedXFDF();
        }
        modal.style.display = 'block';
    };

    // Container for the XFDF code block
    const container = document.createElement('div');
    container.className = 'xfdf-container';

    // Code block for XFDF
    const codePre = document.createElement('pre');
    codePre.className = 'xfdf-pre';

    const codeBlock = document.createElement('code');
    codeBlock.id = 'xfdf-code';
    codeBlock.contentEditable = true;
    codeBlock.textContent = getFormattedXFDF();

    // Assemle the XFDF code block
    codePre.appendChild(codeBlock);
    container.appendChild(codePre);
    wrapper.appendChild(container);
    wrapper.appendChild(button);
    return wrapper;
};

// Export XFDF button
const exportButton = (instance) => {
    const button = document.createElement('button');
    button.className = 'btn-export';
    button.textContent = 'View Document XFDF';
    button.onclick = async () => {
        await extractXFDFFromDocument(instance);
        const codeBlock = document.querySelector('#xfdf-code');
        if (codeBlock) {
            codeBlock.textContent = getFormattedXFDF();
        }
    };
    return button;
};

// Import XFDF button
const importButton = (instance) => {
    const button = document.createElement('button');
    button.className = 'btn-import';
    button.textContent = 'Update Document XFDF';
    button.onclick = async () => {
        await getCodeFromCodeBlock(instance);
    };
    return button;
};

const createUIControls = (instance) => {
    // Create a container for all controls
    const controlsContainer = document.createElement('div');
    controlsContainer.className = 'button-container';

    // Insert the XFDF modal in the right panel (Notes Panel)
    const notesPanel = instance.UI.NotesPanel;
    if (notesPanel) {
        // Create the modal and append it to the notes panel container
        const modal = xfdfModal(instance);
        // Try to find the notes panel element in the DOM
        setTimeout(() => {
            const notesPanelElement = document.querySelector('[data-element="notesPanel"]') ||
                document.querySelector('.NotesPanel') ||
                document.querySelector('[class*="notes-panel"]');

            if (notesPanelElement) {
                notesPanelElement.appendChild(modal);
            } else {
                // Fallback: append to document body
                document.body.appendChild(modal);
            }
        }, 1000); // Wait for UI to be ready
    } else {
        // Fallback: add to main element
        element.insertBefore(xfdfModal(instance), element.firstChild);
    }

    // Add the file picker and Import/Export buttons to the controls container
    controlsContainer.appendChild(filePicker(instance));
    controlsContainer.appendChild(exportButton(instance));
    controlsContainer.appendChild(importButton(instance));

    // Add the XFDF code block element and controls container to the viewer
    element.insertBefore(xfdfElement(), element.firstChild);
    element.insertBefore(controlsContainer, element.firstChild);
};
</code></pre>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/get-started/samples/showcase-demo-xfdf-annotations-in-pdf.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
