> 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/edit-page-content/text-edit.md).

# Edit text and images in PDFs using the WebViewer JavaScript library

Enhance PDF files with WebViewer's content edit feature. Edit text and images directly on PDFs. Enable this feature in the UI for easy editing. Try it out now! The Apryse Web SDK streamlines secure, s

Starting with WebViewer 10.3, you can edit text and images directly within PDF files using WYSIWYG PDF content editing.

This guide explains how to edit text and images in PDF documents using the WebViewer UI. It also describes how to perform the same tasks programmatically using the WebViewer Core APIs. It's intended for two audiences:

* Product users who need clear, step‑by‑step instructions for editing text and images in the UI.
* Engineers who need copy‑ready API examples and practical integration notes.

<figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FvV8v3K6fGqPd0GU1bpm3%2Ff554a027faedf3285d835a2d34b34a6173cf96fe-1483x666.png?alt=media&amp;token=8dbc9a13-51e9-4643-8205-e572104c58af" alt="Apryse WebViewer content editing interface showing editable text regions within a PDF document and formatting controls for modifying text styles, alignment, and colors."><figcaption><p>Edit text and images directly within PDF files using WebViewer's content editing feature.</p></figcaption></figure>

{% hint style="info" %}
**Info:** [PDF Editing](https://apryse.com/capabilities#PDFEditing) is a separate add-on and isn't included in a standard WebViewer license. To try it without the add-on, comment out your license key to run WebViewer in demo mode.
{% endhint %}

{% hint style="warning" %}
**Warning:** The PDF content editing feature only works with PDF files. To edit Word documents, try our [DOCX Editor](https://apryse.com/capabilities/docx-editor). For other file types, call the `loadDocument()` function with the `loadAsPDF: true` option. For example, `documentViewer.loadDocument('myfile.xlsx', { loadAsPDF: true })`.
{% endhint %}

## Configure content edit

Before you begin using the content editing feature in the UI or programmatically, you must enable the following `ContentEdit` feature flag:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  // options
}, document.getElementById('viewer'))
.then((instance) => {
  // Enable the tools to edit the PDF content
  instance.UI.enableFeatures([instance.UI.Feature.ContentEdit]);
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Edit content in the UI

The following sections walk you through entering content edit mode and performing common editing tasks, such as updating text, replacing images, moving content, and resizing elements in the WebViewer UI.

### Access content edit

Use these steps to access content edit mode in the WebViewer UI. Before you start, [enable the content edit feature](#configure-content-edit).

1. Load the viewer.
2. After the WebViewer UI loads, the **Edit Text** toolbar group appears.
3. Click **Edit Text** to enter content edit mode. Once in content edit mode, editable text and images appear with a dashed box around them. A **Text Styles** panel also opens with text editing tools and formatting controls.

{% hint style="info" %}
**Info:** Entering PDF content edit mode changes how the viewer interprets clicks and keyboard input. Clicks inside dashed boxes edit content rather than selecting annotations.
{% endhint %}

### Move, resize, or delete content

Use PDF content editing to modify the placement and size of existing content, or remove content you no longer need.

1. **Move:** Select the content box and drag it to a new location.
2. **Resize:** Drag a handle on the content box to adjust its width or height.
3. **Delete:** Press **Delete** or select **Delete** from the annotation popup.

### Edit existing text

Use the content edit feature to edit text directly within a PDF and update formatting such as font, size, color, and alignment.

1. Locate the dashed box around the text you want to change.
2. Open the editor.
3. Double‑click the dashed box to enter inline edit mode. Optionally, single‑click to select the box, then click the edit button in the annotation popup.
4. Make changes using the inline editor or the right panel tools (font, size, color, alignment).
5. Depending on your configuration, commit changes by clicking outside the box or pressing Enter.

{% hint style="info" %}
**Info:** While editing, the editor applies typed characters to the content box. When you finish, the page updates.
{% endhint %}

### Add paragraphs

With PDF content editing, you can add new text content anywhere in the document by creating and formatting a paragraph box.

1. Click the **Add Paragraph** icon in the toolbar.
2. Click and drag on the page to draw a rectangle where you want the paragraph to appear.
3. Start typing inside the new box.
4. Use the right panel to set font, size, color, and alignment.

<figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2F37d2OwOvRjZZLYJm2gFd%2F8db4c6175b65b1bd1c2ce4322e72cdd14ca57afd-817x581.gif?alt=media&amp;token=940af4d5-24d3-48db-bb9a-68de92182a80" alt="User creating a paragraph box and adding new text to a PDF using the content edit toolbar."><figcaption><p>Use the Add Paragraph tool to place and format new text content in a PDF.</p></figcaption></figure>

### Add images

With content edit, you can add images to your document and position them where needed.

1. Click the **Add Image** icon in the toolbar.
2. Click the document where you want to place the image. The system file picker opens.
3. Select an image file. The image appears in a content box.
4. Resize or move the image as needed.

## Edit content via API

If you're using WebViewer Core directly, content edit APIs are available for working with editable PDF content. For a practical example, see the [webviewer-custom-ui](https://github.com/ApryseSDK/webviewer-samples/tree/main/webviewer-custom-ui) GitHub sample, which uses these APIs.

{% hint style="info" %}
**API Change in WebViewer 12.2.0**

Beginning with WebViewer 12.2.0, content edit APIs have moved from the `ContentEdit` namespace to the `ContentEditManager` namespace.
{% endhint %}

### Manage content edit mode

For programmatic control, call the API to start and end content editing. Before you start, [enable the content edit feature](#configure-content-edit).

To begin editing content, call the [startContentEditMode](https://sdk.apryse.com/api/web/Core.ContentEditManager.html#startContentEditMode__anchor) method. To end content edit mode, you can call the [endContentEditMode](https://sdk.apryse.com/api/web/Core.ContentEditManager.html#endContentEditMode__anchor) method.

{% tabs %}
{% tab title="JavaScript (SDK v12.2+)" %}
{% code lineNumbers="true" %}

```js
// Start content edit mode 
const { documentViewer }  =  instance.Core; 
const contentEditManager  =  documentViewer.getContentEditManager(); 
await contentEditManager.startContentEditMode(); 

// End content edit mode 
const { documentViewer } = instance.Core; 
const contentEditManager = documentViewer.getContentEditManager(); 
await contentEditManager.endContentEditMode(); 
```

{% endcode %}
{% endtab %}
{% endtabs %}

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2FckeGJhAoUlP0ehb2vSI1%2F31b19c20542b2ef7d5628767a6909c04d2e06650-1907x893.png?alt=media\&token=1a70b59e-3057-445c-b15b-778133f8cd08)

The first time the tool mode switches to content edit mode, WebAssembly files will load. To do this earlier, use the [preloadWorker](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.preloadWorker__anchor) method.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
Core.ContentEdit.preloadWorker(myDocumentViewer);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Add paragraphs

Use the [AddParagraphTool](https://sdk.apryse.com/api/web/Core.Tools.AddParagraphTool.html) to add new paragraphs to the document. The following example switches the viewer to paragraph insertion mode.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const addParagraphTool = myDocumentViewer.getTool(Core.Tools.ToolNames.ADD_PARAGRAPH);
myDocumentViewer.setToolMode(addParagraphTool);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Add images

You can also add images by switching to the [AddImageContentTool](https://sdk.apryse.com/api/web/Core.Tools.AddImageContentTool.html).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const addImageContentTool = myDocumentViewer.getTool(Core.Tools.ToolNames.ADD_IMAGE_CONTENT);
myDocumentViewer.setToolMode(addImageContentTool);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Identify annotations

You can tell if an annotation is a content edit placeholder annotation by using the [annotation.isContentEditPlaceHolder](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#isContentEditPlaceholder) API. With those annotations, you can use the [getDocumentContent](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.getDocumentContent) and [updateDocumentContent](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.updateDocumentContent__anchor) APIs as in the following example.

{% tabs %}
{% tab title="JavaScript (SDK v12.2+)" %}
{% code lineNumbers="true" %}

```js
const selectedAnnotation = annotationManager.getSelectedAnnotations()[0];

if (selectedAnnotation?.isContentEditPlaceholder?.()) {
  const contentEditManager = instance.Core.documentViewer.getContentEditManager();
  const textDetails = await contentEditManager.getContent(selectedAnnotation);
  // textDetails.xmlString or textDetails.spanObjects
  // pass/convert this to your editor format (e.g. Quill Delta)
}
```

{% endcode %}
{% endtab %}

{% tab title="JavaScript (SDK v10.7+)" %}
{% code lineNumbers="true" %}

```js
// is the currently selected annotation a placeholder
const selectedAnnotation = annotationManager.getSelectedAnnotations()[0];
if (selectedAnnotation && selectedAnnotation.isContentEditPlaceholder()) {
  const content = await Core.ContentEdit.getDocumentContent(selectedAnnotation);

  // pass content to library that can display rich text, for example Quill
}


// later after the content has been updated this will update it on the page
await Core.ContentEdit.updateDocumentContent(annotation, newContent);
```

{% endcode %}
{% endtab %}
{% endtabs %}

To check whether the annotation is for text or an image, use [getContentEditType](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#getContentEditType) as in this example.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
if (annotation.getContentEditType() === Core.ContentEditTypes.TEXT) {
  // this has text that can be updated
} else if (annotation.getContentEditType() === Core.ContentEditTypes.OBJECT) {
  // this has the image that can be updated
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Move or delete content

When you move or delete the annotations representing the content in the DocumentViewer, the content updates automatically on the page. You can also use the normal annotation APIs.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
annotationManager.deleteAnnotation(myContentEditAnnotation);

myOtherContentEditAnnotation.X = 50;
annotationManager.trigger(Core.AnnotationManager.Events.ANNOTATION_CHANGED, ['modify', [myOtherContentEditAnnotation], {}]);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Get text styles

When editing text in a content edit box, you can retrieve the current text attributes for the selected content. This is useful for updating UI controls or displaying formatting changes as the user moves through the text.

First, attach a function to the [contentBoxEditStarted](https://sdk.apryse.com/api/web/Core.ContentEditManager.html) event to store the reference to the active content box editor.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const handleEditorStarted = ({ editor }) => {
  // Store this editor reference for use later.
  // We will assume that reference is a variable 'contentEditorRef'.
  contentEditorRef = editor;
};
contentEditManager.addEventListener('contentBoxEditStarted', handleEditorStarted);
```

{% endcode %}

**Related links**

* [ContentEditManager#contentBoxEditStarted](https://sdk.apryse.com/api/web/Core.ContentEditManager.html)
  {% endtab %}
  {% endtabs %}

Secondly, attach a function to the [contentEditSelectionChange](https://sdk.apryse.com/api/web/Core.ContentEditManager.html) event. This function should call the referenced editor we stored earlier to retrieve the text attributes with the [getTextAttributes](https://sdk.apryse.com/api/web/Core.ContentEdit.ContentBoxEditor.html#getTextAttributes__anchor) method. Once we obtain the attributes from the current selection, we can extract the various properties.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const handleSelectionChange = async () => {
  if (contentEditorRef) {
    const attribute = await contentEditorRef.getTextAttributes();
    const { fontSize, fontName, fontColor, bold, italic, underline, textAlign } = attribute;
  }
};
contentEditManager.addEventListener('contentEditSelectionChange', handleSelectionChange);
```

{% endcode %}

**Related links**

* [ContentEditManager#contentEditSelectionChange](https://sdk.apryse.com/api/web/Core.ContentEditManager.html)
* [ContentBoxEditor#getTextAttributes](https://sdk.apryse.com/api/web/Core.ContentEdit.ContentBoxEditor.html#getTextAttributes__anchor)
  {% endtab %}
  {% endtabs %}

You can extract the text style (for example, font size and color) from a content edit box that isn't in edit mode. This process fetches the first instance of each property. For example, if a box has both 12-point and 30-point fonts, with the 12-point appearing first, the output will be 12 points.

To do this:

* Select an annotation.
* Obtain the `contentEditBoxId`.
* Request the content box attributes from the `contentEditManager`.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const contentBoxId = annotation.getCustomData('contentEditBoxId');
const contentEditManager = Core.getDocumentViewer().getContentEditManager();
const textAttributes = await contentEditManager.getContentBoxAttributes(contentBoxId);
```

{% endcode %}

**Related links**

* [ContentEditManager#getContentBoxAttributes](https://sdk.apryse.com/api/web/Core.ContentEditManager.html#getContentBoxAttributes__anchor)
* [Annotation#getCustomData](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#getCustomData__anchor)
  {% endtab %}
  {% endtabs %}

### Set text styles

Use the `setTextAttributes` API to apply font and text styling programmatically. The specified attributes are applied to newly entered text and remain active until changed by another API call or user selection.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const fontStyle = {
    "bold": false,
    "italic": false,
    "underline": true,
    "fontName": "Cousine",
    "fontSize": "12",
    "fontColor": "#FF00E8"
}
Core.ContentEdit.setTextAttributes(fontStyle);
```

{% endcode %}

**Related links**

* [ContentEdit#setTextAttributes](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.setTextAttributes__anchor)
  {% endtab %}
  {% endtabs %}

### Undo and redo changes

You can programmatically undo or redo changes made during PDF text edits. This functionality includes actions such as character additions/deletions, style modifications, and content box adjustments. These changes are managed through the [ContentEditHistoryManager](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) class.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// to undo a change
await Core.getDocumentViewer().getContentEditHistoryManager().undo();
// to redo a change
await Core.getDocumentViewer().getContentEditHistoryManager().redo();
// to check if there is an available change to undo
const canUndo = Core.getDocumentViewer().getContentEditHistoryManager().canUndo();
// to check if there is an available change to redo
const canRedo = Core.getDocumentViewer().getContentEditHistoryManager().canRedo();
```

{% endcode %}

**Related links**

* [ContentEditHistoryManager#undo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html)
* [ContentEditHistoryManager#redo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html)
* [ContentEditHistoryManager#canUndo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html)
* [ContentEditHistoryManager#canRedo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html)
  {% endtab %}
  {% endtabs %}

The [undo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) function reverses the most recent change, while the [redo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) function re-applies a previously undone change. Before performing an action, use the [canUndo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) and [canRedo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) functions to determine whether an undo or redo operation is available.

### Detect link annotations

Starting with WebViewer 10.7, a link annotation popup appears when you hover over a link annotation. The popup shows the link URL and lets you remove the link from the document.

<figure><img src="https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2F08vyrW6D1JNol1M68Fk4%2F9fc3940d14da9ae605649e1adcb278421fb865ba-1401x805.gif?alt=media&amp;token=2c4ca09c-dec6-4a5a-be0b-349ca819475f" alt="Animated demonstration of adding a hyperlink to text in a PDF. A user selects text in the content edit editor, opens the Insert Link dialog, enters a URL, and applies the link to the selected text."><figcaption><p>Add hyperlinks to PDF text directly within the content edit editor.</p></figcaption></figure>

Currently, there is no direct API to remove a hyperlink added in content edit mode. However, you can use an alternative approach to detect the link annotation, store it, and then delete it.

To detect the link annotation, utilize the [mouseMove](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:mouseMove__anchor) event in conjunction with the available API [getAnnotationsByMouseEvent](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getAnnotationsByMouseEvent__anchor).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  initialDoc: 'https://myserver.com/myfile.pdf'
}, document.getElementById('viewer'))
  .then(instance => {
    const { documentViewer, annotationManager } = instance.Core;

    documentViewer.addEventListener('mouseMove', evt => {
      const annotations = Core.getAnnotationManager().getAnnotationsByMouseEvent(evt, true);
      const linkAnnot = annotations.find((annot) => annot instanceof window.Core.Annotations.Link);
    });
  });
```

{% endcode %}

**Related links**

* [AnnotationManager.getAnnotationsByMouseEvent](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getAnnotationsByMouseEvent)
  {% endtab %}
  {% endtabs %}

### Delete link annotations

Link annotations can originate from hyperlinks added in WebViewer, links created in content edit mode, or URLs already embedded in the document. Regardless of how a link annotation was created, you can remove it using the [deleteAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#deleteAnnotations__anchor) method. Note that link styling remains visible after you delete the annotation.

The following example removes link annotations generated by WebViewer.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  initialDoc: 'https://myserver.com/myfile.pdf'
}, document.getElementById('viewer'))
  .then(instance => {
    const { annotationManager } = instance.Core;
    const linkAnnotations = annotationManager.getAnnotationsList().filter((annot) => annot instanceof window.Core.Annotations.Link);
    // add {'source': 'unlink'} to let content edit worker know to refresh cached data
    annotationManager.deleteAnnotations(linkAnnotations, { 'source': 'unlink' });
  });
```

{% endcode %}

**Related links**

* [WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html)
* [AnnotationManager.getAnnotationsList](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getAnnotationsList__anchor)
* [AnnotationManager.deleteAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#deleteAnnotations__anchor)
* [AnnotationManager.getGroupAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getGroupAnnotations__anchor)
* [AnnotationManager.ungroupAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#ungroupAnnotations__anchor)
  {% endtab %}
  {% endtabs %}


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.apryse.com/web/edit-page-content/text-edit.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
