This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/headless/link.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Headless link annotations | Nutrient Web SDK

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 classUse case
URIActionExternal URL or Nutrient-specific scheme.
GoToActionJump to another page or position within the current document.
JavaScriptActionRun a JavaScript expression. Subject to the SDK’s scripting permissions.
LaunchActionLaunch an external application or open an embedded file.
HideActionHide or show one or more form fields by name.
ResetFormActionReset form fields to their default values.
SubmitFormActionSubmit form data to a URL.

All action classes live under NutrientViewer.Actions.

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);
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.

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();
}
}
}
});