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

ImageAnnotation lets you embed an image directly into a PDF page. Most of the time, the image content lives in an attachment that the SDK manages. Call instance.createAttachment(blob) to upload the file, and then reference the returned attachment ID from the annotation.

Looking for the visual side of this? See the tools.main slot for replacing the bundled “add image” toolbar entry. This page covers the programmatic surface you’ll call from inside that custom UI.

When to use this

Reach for the headless image API when your workflow involves:

  • A host-app file upload control that adds an image to the current page on submit.
  • A drop target on the viewer that creates an image annotation at the drop coordinates.
  • A workflow that adds a logo, watermark, or photo to the document in response to an external trigger.
  • Bulk-importing images from a backend and placing them at known coordinates.

API reference

SymbolNotes
NutrientViewer.Annotations.ImageAnnotationThe annotation class for placed images.
instance.createAttachment(blob)Uploads a Blob or File as an attachment and returns the attachment ID to use in the annotation.

Supported image formats include PNG and JPEG. Other formats may be supported depending on the SDK build. Refer to the API reference for the authoritative list.

Example: Add an image from a file upload

This example wires a file input to create an image annotation on the current page. The flow is: read the file, upload it as an attachment, and then create the annotation.

const instance = await NutrientViewer.load({
container: "#viewer",
document: "contract.pdf"
});
document.getElementById("image-upload").onchange = async (event) => {
const file = event.target.files[0];
if (!file) return;
const attachmentId = await instance.createAttachment(file);
const annotation = new NutrientViewer.Annotations.ImageAnnotation({
pageIndex: instance.viewState.currentPageIndex,
contentType: file.type,
imageAttachmentId: attachmentId,
description: file.name,
boundingBox: new NutrientViewer.Geometry.Rect({
left: 100,
top: 100,
width: 300,
height: 200
})
});
await instance.create(annotation);
};

The description field is optional but recommended, as it shows up in screen readers and PDF accessibility metadata.

Example: Drag-and-drop image drop target

Wire the viewer container as a drop target so users can drag an image file directly onto the page:

const container = document.getElementById("viewer");
container.addEventListener("dragover", (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "copy";
});
container.addEventListener("drop", async (event) => {
event.preventDefault();
const file = event.dataTransfer.files[0];
if (!file?.type.startsWith("image/")) return;
const attachmentId = await instance.createAttachment(file);
const rect = container.getBoundingClientRect();
const annotation = new NutrientViewer.Annotations.ImageAnnotation({
pageIndex: instance.viewState.currentPageIndex,
contentType: file.type,
imageAttachmentId: attachmentId,
boundingBox: new NutrientViewer.Geometry.Rect({
left: event.clientX - rect.left,
top: event.clientY - rect.top,
width: 200,
height: 200
})
});
await instance.create(annotation);
});

For a production drop target, convert client coordinates to PDF page coordinates rather than placing the annotation at the raw client position.

Example: Bulk-import images from a server

When the images come from a server, fetch them as blobs first. Then create the attachments and annotations together:

async function importLogosFrom(urls) {
const annotations = [];
for (const url of urls) {
const response = await fetch(url);
const blob = await response.blob();
const attachmentId = await instance.createAttachment(blob);
annotations.push(
new NutrientViewer.Annotations.ImageAnnotation({
pageIndex: 0,
contentType: blob.type,
imageAttachmentId: attachmentId,
boundingBox: new NutrientViewer.Geometry.Rect({
left: 50,
top: 50,
width: 100,
height: 100
})
})
);
}
await instance.create(annotations);
}

When to use stamps vs. images

Both StampAnnotation and ImageAnnotation can render an image on a page. The difference is intent:

  • Stamps are reusable templates. They’re driven by instance.stampAnnotationTemplates and instance.setStampAnnotationTemplates(), can be picked from a stamp picker, and are designed for repeat application.
  • Images are one-off placements. There’s no “image template” concept. Each image annotation is unique to its page.

If your workflow involves picking from a library, use stamps. If it involves placing a specific image at a specific location, use images.

  • Add an image — The cross-cutting create-edit-remove guide for image annotations.
  • Headless stamps — The related path when you need reusable image templates.