Headless link annotations
A LinkAnnotation is a clickable region on a PDF page paired with an Action. The default link editor handles the common case — internal navigation and external URLs — but the SDK supports every PDF action type the specification defines. Build links programmatically when you need an action type the editor doesn’t expose, when you’re importing link metadata from a server, or when the link needs to be created in response to a workflow rather than a user click.
Looking for the visual side of this? See the annotations.link slot for replacing the link editor that appears when the user creates or edits a link annotation. This page covers the programmatic surface you’ll call from inside that custom UI.
When to use this
Reach for the headless link API when you’re:
- Importing link metadata from a backend — for example, marking up a generated PDF with cross-references that follow a known schema.
- Creating links with action types the default editor doesn’t expose, such as form submission or JavaScript actions.
- Building a custom link editor that walks the user through every action type with your own labels and validation.
- Bulk-creating links across multiple pages from a single workflow.
Action types
Every link annotation has an action property. The action determines what happens when the link is clicked.
| Action class | Use case |
|---|---|
URIAction | External URL or Nutrient-specific scheme. |
GoToAction | Jump to another page or position within the current document. |
JavaScriptAction | Run a JavaScript expression. Subject to the SDK’s scripting permissions. |
LaunchAction | Launch an external application or open an embedded file. |
HideAction | Hide or show one or more form fields by name. |
ResetFormAction | Reset form fields to their default values. |
SubmitFormAction | Submit form data to a URL. |
All action classes live under NutrientViewer.Actions.
Example: A link to another page
This is the common case, that of a link that jumps to a target page in the same document:
const { LinkAnnotation } = NutrientViewer.Annotations;const { GoToAction } = NutrientViewer.Actions;const { Rect } = NutrientViewer.Geometry;
const link = new LinkAnnotation({ pageIndex: 0, boundingBox: new Rect({ left: 100, top: 100, width: 200, height: 24 }), action: new GoToAction({ pageIndex: 4 })});
await instance.create(link);Example: A link to an external URL
const { URIAction } = NutrientViewer.Actions;
const link = new LinkAnnotation({ pageIndex: 0, boundingBox: new Rect({ left: 100, top: 200, width: 200, height: 24 }), action: new URIAction({ uri: "https://www.nutrient.io/" })});
await instance.create(link);Example: A JavaScript action
JavaScript actions run a JavaScript expression when the link is clicked. They’re powerful but constrained, they only run when the SDK’s scripting support is enabled, and they’re sandboxed to the document context:
const { JavaScriptAction } = NutrientViewer.Actions;
const link = new LinkAnnotation({ pageIndex: 0, boundingBox: new Rect({ left: 100, top: 250, width: 200, height: 24 }), action: new JavaScriptAction({ script: "app.alert('Hello from a PDF action.');" })});
await instance.create(link);Example: A form-control action
Use ResetFormAction, SubmitFormAction, or HideAction to drive form workflows from a click. The example below resets the named form fields when the link is clicked:
const { ResetFormAction } = NutrientViewer.Actions;const { List } = NutrientViewer.Immutable;
const link = new LinkAnnotation({ pageIndex: 0, boundingBox: new Rect({ left: 100, top: 300, width: 200, height: 24 }), action: new ResetFormAction({ fields: List(["name", "email", "phone"]) })});
await instance.create(link);HideAction follows the same pattern but takes a hide Boolean alongside the field list. SubmitFormAction takes a uri and an includeExclude field set.
Example: Bulk-creating links from a server-side schema
When the link metadata comes from a server, build the action class dynamically from the schema and reuse a single create call:
const links = [ { page: 0, x: 100, y: 100, type: "uri", uri: "https://www.nutrient.io/" }, { page: 0, x: 100, y: 150, type: "goto", target: 5 }, { page: 1, x: 200, y: 100, type: "javascript", script: "console.log('clicked');" }];
const annotations = links.map((data) => { let action; switch (data.type) { case "uri": action = new NutrientViewer.Actions.URIAction({ uri: data.uri }); break; case "goto": action = new NutrientViewer.Actions.GoToAction({ pageIndex: data.target }); break; case "javascript": action = new NutrientViewer.Actions.JavaScriptAction({ script: data.script }); break; }
return new NutrientViewer.Annotations.LinkAnnotation({ pageIndex: data.page, boundingBox: new NutrientViewer.Geometry.Rect({ left: data.x, top: data.y, width: 200, height: 24 }), action });});
await instance.create(annotations);Customizing click behavior
Listen for annotations.press to intercept link clicks before the SDK runs the action:
instance.addEventListener("annotations.press", (event) => { if (event.annotation instanceof NutrientViewer.Annotations.LinkAnnotation) { const action = event.annotation.action; if (action instanceof NutrientViewer.Actions.URIAction) { const ok = window.confirm(`Open ${action.uri}?`); if (!ok) { event.preventDefault(); } } }});Related
- Color presets — Read link annotation color presets for a custom picker.
- Link annotations reference — The
LinkAnnotationclass details. - PDF actions support — The broader story for PDF action handling.