---
title: "Headless image annotations | Nutrient Web SDK"
canonical_url: "https://www.nutrient.io/guides/web/headless/image/"
md_url: "https://www.nutrient.io/guides/web/headless/image.md"
last_updated: "2026-05-15T19:10:05.084Z"
description: "Create image annotations from a host-app file upload, URL, or attachment with Nutrient Web SDK, no built-in image picker required."
---

# Headless image annotations

`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](https://www.nutrient.io/guides/web/user-interface/ui-customization/supported-slots.md#tools) 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

| Symbol                                       | Notes                                                                                               |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `NutrientViewer.Annotations.ImageAnnotation` | The 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](https://www.nutrient.io/api/web/classes/NutrientViewer.Annotations.ImageAnnotation.html) 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.

```js

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:

```js

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:

```js

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.

## Related

- [Add an image](https://www.nutrient.io/guides/web/annotations/create-edit-and-remove/add-image.md) — The cross-cutting create-edit-remove guide for image annotations.

- [Headless stamps](https://www.nutrient.io/guides/web/headless/stamp.md) — The related path when you need reusable image templates.
---

## Related pages

- [Headless callout annotations](/guides/web/headless/callout.md)
- [Headless color presets](/guides/web/headless/color-presets.md)
- [Annotation clipboard](/guides/web/headless/clipboard.md)
- [Headless](/guides/web/headless.md)
- [Headless ink annotations](/guides/web/headless/ink.md)
- [Headless stamp annotations](/guides/web/headless/stamp.md)
- [Headless link annotations](/guides/web/headless/link.md)
- [Headless note annotations](/guides/web/headless/note.md)
- [Redactions from text selection](/guides/web/headless/redactions-from-selection.md)
- [Programmatic notes panel](/guides/web/headless/notes-panel.md)
- [Headless text annotations](/guides/web/headless/text-annotations.md)
- [Headless shape annotations](/guides/web/headless/shape.md)
- [Headless text markup](/guides/web/headless/text-markup.md)

