---
title: "Customizing annotations | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/web/annotations/custom-rendered-annotations/"
md_url: "https://www.nutrient.io/guides/web/annotations/custom-rendered-annotations.md"
last_updated: "2026-05-30T02:20:01.385Z"
description: "At Nutrient, we created an API that allows customers to override or decorate specific parts of the UI when rendering annotations."
---

# Annotation customization

At Nutrient, we created an API that enables you to override or decorate specific parts of the user interface (UI) when rendering annotations.

We call these custom renderers, and they let you hook into the annotation render pipeline.

For driving annotation behavior from your own UI without touching our built-in UI, refer to [headless annotations](https://www.nutrient.io/guides/web/headless.md), our per-type guides for ink, text, markup, measurements, shapes, links, redactions, stamps, images, notes, and callouts. To replace the UI rendered around an annotation (tooltips, popovers, action buttons), refer to the annotation slots in the [supported slots reference](https://www.nutrient.io/guides/web/user-interface/ui-customization/supported-slots.md). Custom renderers, which are discussed on this page, are still the correct tool when you want to decorate or replace the appearance of an annotation itself on the page.

[Try for free](https://www.nutrient.io/sdk/web/getting-started.md)

[Launch demo](https://www.nutrient.io/demo/annotations/)

These renderers are functions that take properties as input and return a DOM node reference. When a renderer returns `null`, we instead render the default UI for that slot.

You can register [custom renderers](https://www.nutrient.io/api/web/NutrientViewer.CustomRenderers.html) when initializing Nutrient:

```js

NutrientViewer.load({
  customRenderers: {
    Annotation: ({ annotation }) => {
      if (annotation instanceof NutrientViewer.Annotations.NoteAnnotation) {
        return {
          node: document.createTextNode(`📝 ${annotation.text}`),
          append: false // Replace the entire note annotation UI.
        };
      } else {
        return null; // Render the default UI.
      }
    }
  }
});

```

If you want to get a glimpse of the power of custom rendered annotations, refer to the following two demo examples: [customize annotations](https://www.nutrient.io/demo/annotations/) and [hide/reveal area](https://www.nutrient.io/demo/hide-reveal-area-in-pdf/).

## Using custom renderers

In the case of annotations, the custom renderers API consists of a callback function that’s called by each annotation component at render time. This callback, if provided by the user, must return a [`NutrientViewer.RendererConfiguration`](https://www.nutrient.io/api/web/NutrientViewer.RendererConfiguration.html) object, or `null` if the annotation’s appearance shouldn’t be modified.

The object has the following members:

- `node` — A DOM node. This is the only mandatory field of the returned object if it’s not `null`. Make sure you don’t return the same node for different annotations that are rendered simultaneously, as DOM nodes cannot exist in more than one place at the same time.

- `append` — Determines if the node should be appended to the default annotation appearance (`true`) or replaced altogether (`false`). When `append` is set to `true`, the provided DOM node can be used to enhance the default annotation’s appearance. However, it won’t inherit the default annotation behavior (i.e. it won’t select the annotation when receiving the `pointerdown` event), so such a case would need to be handled by the user.

- `noZoom` — By default, the annotation’s appearance is scaled when the page is zoomed, so the DOM node returned from this callback will be scaled along with it. Opt out of this behavior by setting this property to `true` (defaults to `false`).

- `onDisappear` — An optional callback that will be called whenever the annotation component is unmounted to allow for releasing resources or any other cleanup operation.

For example, assume all the custom renderer’s returned nodes include the corresponding annotation `id` property in a `data-annotation-id` attribute:

```js

instance.contentDocument.addEventListener(
  "pointerdown",
  event => {
    if (event.target && event.target.getAttribute("data-annotation-id")) {
      event.preventDefault();
      event.stopImmediatePropagation();
      instance.setSelectedAnnotation(
        event.target.getAttribute("data-annotation-id")
      );
    }
  },
  { capture: true }
);

```

The code above ensures that when the custom rendered annotation is targeted by a `pointerdown` event, the corresponding annotation is selected so it can be moved, resized, and so on.

## Use case study: Show annotation creator’s name

Since custom renderers let you associate any renderable content with an annotation, it’s possible to extend the default API capabilities with your own features, such as showing the annotation creator’s name:

```js

const annotationRenderer = ({ annotation }) => {
  // Don't show the creator's name if it's not set, or if it's empty.
  if (!annotation.creatorName || annotation.creatorName.length === 0) {
    return null;
  }
  const authorLabel = document.createElement("div");
  // Style your node. You may as well just set the element's class
  // to one of your own, but sometimes you'll also need to set
  // properties individually for each annotation.
  authorLabel.style.cssText = `
        font-family: Helvetica, sans-serif;
        font-size: 1rem;
        padding: 0.5rem 1rem;
        background-color: white;
        color: blue;
        position: absolute;
        left: 50%;
        top: -12px;
        transform: translate(-50%, -100%);
    `;
  // Add the annotation's author name string.
  authorLabel.appendChild(
    document.createTextNode(annotation.creatorName)
  );
  // Return the `NutrientViewer.RendererConfiguration` object.
  return {
    // Return the created node.
    node: authorLabel,
    // Append to the annotation's appearance instead of replacing it.
    append: true,
    // Zoom automatically with the page.
    noZoom: false
  };
};
NutrientViewer.load({
  customRenderers: {
    // Currently, only annotations can be custom rendered.
    Annotation: annotationRenderer
  }
});

```

The DOM node will be appended to the same container the annotation component is mounted in, immediately after the annotation component in DOM order. This parent container has `position: relative` set, so usually you don’t need to know about the annotation’s coordinates unless you require some specific absolute position handling.

## Replacing the annotation’s appearance

Note that when `append` is `false` (the default value), the default appearance of the annotation, including the pointer event listeners, isn’t rendered.

This means that if you want your custom content to select the annotation when clicked, you’ll have to add some logic to support it.

The code below shows how to add an event listener to your node in your custom renderer code and supply a callback to the `onDisappear` property to remove the listener:

```js

NutrientViewer.load({
  customRenderers: {
    Annotation: ({ annotation }) => {
      function selectAnnotation(event) {
        event.stopImmediatePropagation();
        instance.setSelectedAnnotation(annotation.id);
      }
      const node = document.createElement("div").appendChild(document.createTextNode("Custom rendered!"));
      node.addEventListener("pointerdown", selectAnnotation, {
        capture: true
      });
      return {
        node,
        append: false, // `default`=`false`
        onDisappear: () => {
          node.removeEventListener("pointerdown", selectAnnotation, {
            capture: true
          });
        }
      };
    }
  }
});

```

These are just basic examples of the possibilities offered by the custom renderers API, which opens the door to a wide variety of annotation customizations and can greatly enhance the user experience.

## Controlling annotation render order

If you need overlapping annotations to paint in a specific order on the page, use the annotation render-order API with a custom comparator function. This is separate from custom renderers: Custom renderers change what an annotation looks like, while the render-order comparator changes which annotation is drawn in front of another.

This is useful when your workflow depends on a predictable stacking order — for example, ensuring highlights render beneath shapes, or prioritizing certain annotation types visually without changing their data.
---

## Related pages

- [Annotation notes](/guides/web/annotations/customization/annotation-notes.md)
- [Annotation appearance streams](/guides/web/annotations/appearance-streams.md)
- [Configuring annotation presets in our viewer](/guides/web/customizing-the-interface/using-annotation-presets.md)
- [Customizing display logic for annotations](/guides/web/best-practices/business-logic.md)
- [Store custom data in annotations](/guides/web/annotations/custom-data-in-annotations.md)
- [Hiding annotations in our viewer](/guides/web/annotations/customization/hiding-annotations.md)

