---
title: "Annotations in code — Svelte"
description: "Read, create, change, reorder and delete annotations from your own code, with changes shown at once."
framework: "Svelte"
source: "https://www.embedpdf.com/docs/headless/svelte/annotations/with-code"
---

# Annotations in code

Everything people do with the pointer, your code can do too. `useAnnotation()` gives you the
annotations on screen, and every change you make shows at once, before the engine has saved it:

```ts
const { annotation: square } = await annotation.create(page.ref, {
  subtype: 'square',
  box: { x: 72, y: 92, width: 200, height: 100 },
  color: '#0078ff',
});

await annotation.update(square.ref, { contents: 'Check this table' });
await annotation.delete(square.ref);
```

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { localEngine } from '@embedpdf/engine';
  import SquareControls from './SquareControls.svelte';

  import '../create-update.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <SquareControls />
    <Stage class="stage">
      <RenderLayer />
      <AnnotationLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`create-update.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`SquareControls.svelte`**

```svelte
<!-- Create a rectangle, then change, move and delete it, all from code. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import {
    useAnnotation,
    useAnnotationState,
    type AnnotationRef,
  } from '@embedpdf/svelte/annotation';

  const annotation = useAnnotation();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const pages = usePageList();
  const cover = $derived(pages.current[0]?.ref);
  let ref = $state.raw<AnnotationRef | null>(null);

  // Reads named get…, can… and is… follow the annotations as they change.
  const square = $derived(ref ? annotation.get(ref) : null);
  const canUpdate = $derived(square !== null && annotation.canUpdate(square.ref));
  const canDelete = $derived(square !== null && annotation.canDelete(square.ref));
  const status = $derived.by(() => {
    if (!square) return 'No rectangle';
    return annotation.isPending(square.ref) ? 'Saving…' : 'Saved';
  });

  async function add() {
    if (!cover) return;
    const { annotation: made } = await annotation.create(cover, {
      subtype: 'square',
      box: { x: 72, y: 592, width: 200, height: 100 },
      color: '#0078ff',
      strokeWidth: 3,
    });
    ref = made.ref;
  }

  // On load: one rectangle, to change.
  let started = false;
  $effect(() => {
    if (!ready.current || started) return;
    started = true;
    untrack(() => void add());
  });

  function makeRed() {
    if (square) void annotation.update(square.ref, { color: '#dc143c' });
  }

  function moveRight() {
    if (!square) return;
    const { rect } = square;
    void annotation.update(square.ref, { rect: { ...rect, x: rect.x + 40 } }); // 40 points right
  }

  function turn() {
    if (square) void annotation.update(square.ref, { rotation: 90 });
  }

  function remove() {
    if (!square) return;
    void annotation.delete(square.ref);
    ref = null;
  }
</script>

<div class="toolbar">
  <button type="button" class="button" disabled={square !== null} onclick={add}>Create</button>
  <button type="button" class="button" disabled={!canUpdate} onclick={makeRed}>Make it red</button>
  <button type="button" class="button" disabled={!canUpdate} onclick={moveRight}>Move right</button>
  <button type="button" class="button" disabled={!canUpdate} onclick={turn}>Turn to 90°</button>
  <button type="button" class="button" disabled={!canDelete} onclick={remove}>Delete</button>
  <span class="spacer"></span>
  <output class="readout">{status}</output>
</div>
```

> **Try it:** each button is one call. **Saving…** shows while the engine writes a change.

An annotation here is the engine's record: the same kinds, fields and names as the
[engine's annotation pages](https://www.embedpdf.com/docs/engine/annotations). Each kind's page there lists its fields.
These pages cover what the plugin adds on top.

## Read annotations

```ts
annotation.list(); // every annotation, in drawing order
annotation.list({ pages: [page.ref] }); // one page
annotation.list({ subtype: 'highlight' }); // one kind
annotation.get(ref); // one, or null
annotation.getAt(page.ref, { x: 120, y: 80 }); // the topmost one at a point, or null
```

To keep a component up to date, use `useAnnotationList()`. Your component updates
when the list changes:

**`list.svelte`**

```svelte
<script lang="ts">
  import { useAnnotationList } from '@embedpdf/svelte/annotation';

  const highlights = useAnnotationList({ subtype: 'highlight' });
</script>

<p>{highlights.current.length} highlights</p>
```

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { localEngine } from '@embedpdf/engine';
  import AddAnnotations from './AddAnnotations.svelte';
  import AnnotationList from './AnnotationList.svelte';

  import '../list.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <AddAnnotations />
    <div class="viewer">
      <Stage class="stage">
        <RenderLayer />
        <AnnotationLayer />
      </Stage>
      <AnnotationList />
    </div>
  </DocumentGate>
</Viewer>
```

**`list.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* One choice of a few: the pressed button is the current one */
.segmented {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 3px;
  background: var(--tint);
  border: 1px solid var(--line);
  border-radius: 11px;
}

.segmented button {
  font: inherit;
  font-size: 12.5px;
  font-weight: 550;
  color: var(--muted);
  background: transparent;
  border: 0;
  border-radius: 8px;
  padding: 6px 12px;
  cursor: pointer;
  white-space: nowrap;
}

.segmented button:hover {
  color: var(--ink);
}

.segmented button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--surface);
  box-shadow: 0 1px 2px rgb(7 32 76 / 0.1);
}

.segmented button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* The list: a kind filter, then one row per annotation */
.panel .segmented {
  margin-bottom: 8px;
}

.count {
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--muted);
}

.items {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  font: inherit;
  font-size: 13px;
  text-align: left;
  color: var(--text);
  background: transparent;
  border: 0;
  border-radius: 9px;
  padding: 8px 10px;
  cursor: pointer;
}

.item:hover {
  background: var(--tint);
}

.item:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.kind {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The pages and the panel: stacked, and side by side where there's room */
.viewer {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.stage,
.panel {
  flex: 1 1 100%;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.stage {
  height: 340px;
  background: var(--canvas);
}

.panel {
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 12px;
  background: var(--surface);
}

@container (min-width: 620px) {
  .stage {
    flex: 1 1 0;
    min-width: 0;
    height: 420px;
  }

  .panel {
    flex: 0 0 260px;
    max-height: 420px;
  }
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`AddAnnotations.svelte`**

```svelte
<!-- On load: highlights, notes and a rectangle on the first two pages. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useAnnotation, useAnnotationState } from '@embedpdf/svelte/annotation';

  // A line of text, as the four corners a highlight takes.
  const quad = (x: number, y: number, width: number, height: number) => ({
    upperLeft: { x, y },
    upperRight: { x: x + width, y },
    lowerLeft: { x, y: y + height },
    lowerRight: { x: x + width, y: y + height },
  });

  const annotation = useAnnotation();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const pages = usePageList();

  let added = false;
  $effect(() => {
    const [cover, second] = pages.current;
    if (!ready.current || !cover || !second || added) return;
    added = true;
    untrack(() => {
      void annotation.create(cover.ref, {
        subtype: 'highlight',
        quadPoints: [quad(106, 218, 352, 49)],
        color: '#ffcd45',
        contents: 'The title',
      });
      void annotation.create(cover.ref, {
        subtype: 'text',
        rect: { x: 470, y: 232, width: 20, height: 20 },
        contents: 'Can we shorten it?',
        color: '#facc15',
      });
      void annotation.create(second.ref, {
        subtype: 'highlight',
        quadPoints: [quad(57, 57, 242, 36), quad(57, 93, 265, 36)],
        color: '#ffcd45',
        contents: 'The opening line',
      });
      void annotation.create(second.ref, {
        subtype: 'square',
        box: { x: 52, y: 580, width: 470, height: 64 },
        color: '#e5484d',
        strokeWidth: 2,
        contents: 'Rewrite this paragraph',
      });
    });
  });
</script>
```

**`AnnotationList.svelte`**

```svelte
<!-- The annotations of one kind, in drawing order. A click shows one on its page. -->
<script lang="ts">
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useStage } from '@embedpdf/svelte/stage';
  import {
    annotationKey,
    useAnnotation,
    useAnnotationList,
    type Annotation,
    type AnnotationSubtype,
  } from '@embedpdf/svelte/annotation';

  const KINDS: { label: string; subtype?: AnnotationSubtype }[] = [
    { label: 'All' },
    { label: 'Highlights', subtype: 'highlight' },
    { label: 'Notes', subtype: 'text' },
    { label: 'Rectangles', subtype: 'square' },
  ];

  let kind = $state.raw(KINDS[0]!);
  const annotations = useAnnotationList(() =>
    kind.subtype ? { subtype: kind.subtype } : undefined,
  );
  const pages = usePageList();
  const stage = useStage();
  const annotation = useAnnotation();

  const pageNumberOf = (objectNumber: number) =>
    pages.current.findIndex((page) => page.ref.objectNumber === objectNumber) + 1;

  function show(each: Annotation) {
    stage.reveal(each.page, { rect: each.rect });
    annotation.selection.set([each.ref]);
  }
</script>

<div class="panel">
  <div class="segmented" role="group" aria-label="Kind">
    {#each KINDS as each (each.label)}
      <button type="button" aria-pressed={each === kind} onclick={() => (kind = each)}>
        {each.label}
      </button>
    {/each}
  </div>
  <p class="count">{annotations.current.length} found</p>
  <ul class="items">
    {#each annotations.current as each (annotationKey(each.ref))}
      <li>
        <button type="button" class="item" onclick={() => show(each)}>
          <span class="kind">
            {each.subtype} · page {pageNumberOf(each.page.objectNumber)}
          </span>
          <span>{each.contents ?? '—'}</span>
        </button>
      </li>
    {/each}
  </ul>
</div>
```

> **Try it:** pick a kind, then click a row to go to it.

Each annotation has a `subtype` (its kind), a `rect` (where it is on its page), a `page` and a
`ref`. Keep the `ref`: it's how you change or delete the annotation later. Positions are in
points from the top-left of the page; see [Coordinates](../concepts/coordinates).

Annotations are loaded in the background when a document opens. `useAnnotationState()` has a
`status`: `'loading'` until they're all in, then `'ready'`.

## Create an annotation

`create()` takes the page, then the annotation's kind and fields:

```ts
const { annotation: note } = await annotation.create(page.ref, {
  subtype: 'text',
  rect: { x: 500, y: 72, width: 20, height: 20 },
  contents: 'Is this total right?',
});
```

Fields you leave out get the kind's default. For example, a new annotation prints with the page
unless you pass `print: false`. Every kind the engine can create, the plugin can too; the
[engine's pages](https://www.embedpdf.com/docs/engine/annotations) show each one.

Some options change how the plugin treats the new annotation:

```ts
await annotation.create(page.ref, { subtype: 'square', box }, undefined, {
  tool: 'square',
  select: true,
});
```

| Option   | What it does                                                                                   |
| :------- | :--------------------------------------------------------------------------------------------- |
| `tool`   | Starts from that tool's defaults, the user's current drawing style, before your own fields.    |
| `select` | Selects the new annotation, as drawing it would. Without it, code never changes the selection. |

Stamps and file attachments carry bytes. They go in the third argument, `resources`, named for what
they are: a stamp's `appearance`, an attachment's `file`.

```ts
const image = await (await fetch('/approved.png')).blob();
await annotation.create(page.ref, { subtype: 'stamp', box }, { appearance: image });

// A file pinned to the page, drawn as an icon, 20 × 20 like a note
await annotation.create(
  page.ref,
  { subtype: 'file-attachment', rect: { x: 40, y: 60, width: 20, height: 20 } },
  { file: pickedFile }, // a File brings its name and type
);
```

For stamps from a library, see [Stamps](stamps).

### From the selected text

`createFromSelection()` turns the text a person selected into a highlight, an underline, a
strikeout or a squiggly line, one per page:

```ts
const { annotations } = await annotation.createFromSelection('highlight');
```

It takes a tool id, so your own markup tools work too. The text selection is cleared afterwards;
pass `{ clear: false }` to keep it.

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { SelectionLayer, selectionPlugin } from '@embedpdf/svelte/selection';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { localEngine } from '@embedpdf/engine';
  import MarkupToolbar from './MarkupToolbar.svelte';

  import '../from-selection.css';

  const engine = localEngine();
  const plugins = [
    stagePlugin(),
    renderPlugin(),
    interactionPlugin(),
    selectionPlugin(),
    annotationPlugin(),
  ];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <MarkupToolbar />
    <Stage class="stage">
      <RenderLayer />
      <SelectionLayer />
      <AnnotationLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`from-selection.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`MarkupToolbar.svelte`**

```svelte
<!-- The selected text becomes a mark, one per page; the selection is cleared afterwards. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useSelection, useSelectionState } from '@embedpdf/svelte/selection';
  import { useAnnotation } from '@embedpdf/svelte/annotation';

  // On the cover: the characters of the title's first line.
  const FIRST_LINE = { start: 10, count: 17 };

  const MARKUP = [
    { tool: 'highlight', label: 'Highlight' },
    { tool: 'underline', label: 'Underline' },
    { tool: 'strikeout', label: 'Strike out' },
    { tool: 'squiggly', label: 'Squiggly' },
  ];

  const annotation = useAnnotation();
  const selection = useSelection();
  const hasSelection = useSelectionState((state) => state.hasSelection);
  const pages = usePageList();
  let status = $state('');

  // On load: the title's first line is selected, ready to mark.
  $effect(() => {
    const cover = pages.current[0]?.ref;
    if (cover) untrack(() => selection.select({ page: cover, ...FIRST_LINE }));
  });

  async function mark(tool: string, label: string) {
    const { annotations } = await annotation.createFromSelection(tool);
    status = `${label}: ${annotations.length} made`;
  }
</script>

<div class="toolbar">
  {#each MARKUP as { tool, label } (tool)}
    <button
      type="button"
      class="button"
      disabled={!hasSelection.current}
      onclick={() => void mark(tool, label)}
    >
      {label}
    </button>
  {/each}
  <span class="spacer"></span>
  <output class="readout">
    {hasSelection.current ? 'Text selected' : status || 'Select text'}
  </output>
</div>
```

> **Try it:** the title's first line is selected: press **Highlight**. Then select other words
> with the pointer and mark them.

## Change an annotation

`update()` takes the `ref` and only the fields you want to change:

```ts
await annotation.update(ref, { color: '#dc143c', contents: 'Reviewed' });
```

A field you leave out keeps its value, and `null` removes it (`{ interiorColor: null }` for no
fill). You can also pass an annotation you read straight back, which makes "change a copy, then
save it" simple:

```ts
const square = annotation.get(ref);
if (square?.subtype === 'square') await annotation.update(ref, { ...square, opacity: 0.5 });
```

To move or resize any annotation, send a new `rect`:

```ts
const { rect } = annotation.get(ref)!;
await annotation.update(ref, { rect: { ...rect, x: rect.x + 40 } }); // 40 points right
```

For a shape drawn from points, like a line or ink, the engine moves every point for you. The rules
for resizing are on the engine's [Annotations](https://www.embedpdf.com/docs/engine/annotations#move-or-resize-by-rect)
page.

Rotation is a field too: `update(ref, { rotation: 90 })` turns a shape or a stamp to 90° clockwise.

## Delete an annotation

```ts
await annotation.delete(ref);
```

Deleting an annotation also deletes its popup and its replies, as other PDF apps do.

## Change the drawing order

Annotations on a page are drawn in order, and the last one is on top. `reorder()` moves
annotations together, in the order you pass them, next to another annotation of the page or to the
back or the front:

```ts
await annotation.reorder([ref], 'start'); // to the back
await annotation.reorder([ref], 'end'); // to the front
await annotation.reorder([ref], { after: other }); // just above another annotation
```

Form fields are drawn above every annotation and keep their own order, so `reorder()` never
places an annotation above a field.

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { localEngine } from '@embedpdf/engine';
  import AddRectangles from './AddRectangles.svelte';
  import OrderControls from './OrderControls.svelte';

  import '../order.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <AddRectangles />
    <OrderControls />
    <Stage class="stage">
      <RenderLayer />
      <AnnotationLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`order.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`AddRectangles.svelte`**

```svelte
<!-- On load: three filled rectangles stacked on the cover, the middle one selected. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useAnnotation, useAnnotationState } from '@embedpdf/svelte/annotation';

  const COLORS = [
    { color: '#e5484d', fill: '#ffd1d3' },
    { color: '#30a46c', fill: '#c9f0da' },
    { color: '#1e90ff', fill: '#cfe6ff' },
  ];

  const annotation = useAnnotation();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const pages = usePageList();

  let added = false;
  $effect(() => {
    const cover = pages.current[0]?.ref;
    if (!ready.current || !cover || added) return;
    added = true;
    untrack(() => {
      COLORS.forEach(({ color, fill }, index) => {
        void annotation.create(
          cover,
          {
            subtype: 'square',
            box: { x: 300 + index * 50, y: 520 + index * 30, width: 160, height: 90 },
            color,
            interiorColor: fill,
            strokeWidth: 3,
          },
          undefined,
          { select: index === 1 },
        );
      });
    });
  });
</script>
```

**`OrderControls.svelte`**

```svelte
<!-- The last annotation on a page is drawn on top. -->
<script lang="ts">
  import {
    annotationKey,
    useAnnotation,
    useAnnotationList,
    useAnnotationState,
  } from '@embedpdf/svelte/annotation';

  const annotation = useAnnotation();
  const selected = useAnnotationState((state) => state.selected);
  const first = $derived(selected.current[0]);
  const onPage = useAnnotationList(() => (first ? { pages: [first.page] } : undefined));
  const position = $derived(
    first ? onPage.current.findIndex((a) => annotationKey(a.ref) === annotationKey(first.ref)) : -1,
  );

  function sendToBack() {
    if (first) void annotation.reorder([first.ref], 'start');
  }

  function bringToFront() {
    if (first) void annotation.reorder([first.ref], 'end');
  }
</script>

<div class="toolbar">
  <button
    type="button"
    class="button"
    disabled={!first || selected.current.length !== 1}
    onclick={sendToBack}
  >
    Send to back
  </button>
  <button
    type="button"
    class="button"
    disabled={!first || selected.current.length !== 1}
    onclick={bringToFront}
  >
    Bring to front
  </button>
  <span class="spacer"></span>
  <output class="readout">
    {position >= 0
      ? `${position + 1} of ${onPage.current.length}, from the back`
      : 'Select a rectangle'}
  </output>
</div>
```

> **Try it:** send the green rectangle to the back, then select another and bring it to the
> front.

## Changes show at once

A change shows the moment you make it. The engine saves it in the background, and your `await`
resolves once it has. Until then the annotation is **pending**:

```ts
annotation.isPending(ref); // true while the engine is still saving a change to it
```

You rarely need this: `get()` and `list()` already show the change. It's useful for a small
"saving…" hint.

If the engine refuses a change (a lock set by someone else, or permission taken away), the change
disappears again, and an event says why:

**`write-failed.svelte`**

```svelte
<script lang="ts">
  import { useAnnotationEvent } from '@embedpdf/svelte/annotation';

  import { toast } from './toast';

  useAnnotationEvent(
    (annotation) => annotation.onWriteFailed,
    ({ error }) => toast(`Couldn't save: ${error.message}`),
  );
</script>
```

The `await` of the call that made the change rejects with the same error.

## Copy an annotation

A read passed to `create()` makes the same annotation again, on another page or in another
document:

```ts
const copy = annotation.get(ref)!;

await annotation.create(otherPage.ref, copy);
```

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { localEngine } from '@embedpdf/engine';
  import AddAnnotations from './AddAnnotations.svelte';
  import CopyButton from './CopyButton.svelte';

  import '../copy.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), annotationPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <AddAnnotations />
    <CopyButton />
    <Stage class="stage">
      <RenderLayer />
      <AnnotationLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`copy.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --text: #3b4b7a;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 12px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* Pushes what follows to the right */
.spacer {
  flex: 1 1 auto;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The pages */
.stage {
  height: 420px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`AddAnnotations.svelte`**

```svelte
<!-- On load: a rectangle and a text box on the cover, the rectangle selected and in view. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useStage } from '@embedpdf/svelte/stage';
  import { useAnnotation, useAnnotationState } from '@embedpdf/svelte/annotation';

  const annotation = useAnnotation();
  const stage = useStage();
  const ready = useAnnotationState((state) => state.status === 'ready');
  const pages = usePageList();

  let added = false;
  $effect(() => {
    const cover = pages.current[0]?.ref;
    if (!ready.current || !cover || added) return;
    added = true;
    untrack(() => {
      void annotation.create(cover, {
        subtype: 'free-text',
        box: { x: 300, y: 512, width: 230, height: 40 },
        contents: 'Copy me too',
        fontSize: 16,
        fontColor: '#1a2748',
        interiorColor: '#fffbe6',
      });
      void annotation
        .create(
          cover,
          {
            subtype: 'square',
            box: { x: 96, y: 506, width: 178, height: 54 },
            color: '#e5484d',
            interiorColor: '#ffe4e1',
            strokeWidth: 3,
          },
          undefined,
          { select: true },
        )
        .then(({ annotation: made }) => stage.reveal(cover, { rect: made.rect }));
    });
  });
</script>
```

**`CopyButton.svelte`**

```svelte
<!-- A read passed to create() makes the same annotation again, here on the next page. -->
<script lang="ts">
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useStage } from '@embedpdf/svelte/stage';
  import { useAnnotation, useAnnotationState } from '@embedpdf/svelte/annotation';

  const annotation = useAnnotation();
  const stage = useStage();
  const pages = usePageList();
  const selected = useAnnotationState((state) => state.selected);
  let status = $state('');
  const first = $derived(selected.current[0]);
  const index = $derived(
    first
      ? pages.current.findIndex((page) => page.ref.objectNumber === first.page.objectNumber)
      : -1,
  );
  const next = $derived(pages.current[index + 1]);

  async function copyToNextPage() {
    if (!first || !next) return;
    const target = next;
    const copy = annotation.get(first.ref)!;
    const { annotation: made } = await annotation.create(target.ref, copy, undefined, {
      select: true, // so the next click copies the copy, a page further
    });
    stage.reveal(target.ref, { rect: made.rect });
    status = `Copied to page ${target.index + 1}`;
  }
</script>

<div class="toolbar">
  <button
    type="button"
    class="button"
    disabled={selected.current.length !== 1 || !next}
    onclick={() => void copyToNextPage()}
  >
    Copy to the next page
  </button>
  <span class="spacer"></span>
  <output class="readout">
    {status || (first ? 'Ready to copy' : 'Select an annotation to copy')}
  </output>
</div>
```

> **Try it:** copy the rectangle, then copy the copy: it moves a page further each time. Select the
> text box to copy it instead.

Stamps and file attachments also need their bytes. Download them first:

```ts
const drawing = await annotation.downloadResource(stamp.ref, 'appearance');
await annotation.create(otherPage.ref, stamp, { appearance: drawing });

const file = await annotation.downloadResource(attachment.ref, 'file');
await annotation.create(otherPage.ref, attachment, { file });
```

To copy on the **same page**, clear its unique name first: `{ ...copy, nm: null }`. A name is
unique per page, so a copy with a taken name is refused.

To copy many annotations at once, use [export and import](saving#export-and-import).

## Methods

What `useAnnotation()` gives you directly. Its parts have their own pages: `selection`, `draft` and
`text` are on [Selecting & editing](selecting#methods), `tools` and `stamps` on
[Tools & defaults](tools#methods), and `comments` on [Comments & replies](comments#methods).

| Method                                                         | What it does                                                                                                                                                                                                                                     |
| :------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `get(ref)`                                                     | One annotation as it shows now, pending changes included, or `null`                                                                                                                                                                              |
| `list({ pages?, subtype? })`                                   | Every annotation in drawing order, or the ones on some pages or of one kind. In a component, use `useAnnotationList(filter)`. [Read annotations](#read-annotations)                                                                              |
| `getAt(page, point)`                                           | The topmost annotation at a point on a page, or `null`. A selected annotation's handles count as the annotation.                                                                                                                                 |
| `isPending(ref)`                                               | Whether the engine is still saving a change to it. [Changes show at once](#changes-show-at-once)                                                                                                                                                 |
| `create(page, fields, resources?, { tool?, select? })`         | Creates an annotation from its kind and fields, and shows it at once. A stamp's drawing or an attachment's file goes in `resources`, as `{ appearance }` or `{ file }`. Resolves `{ annotation }`. [Create an annotation](#create-an-annotation) |
| `createFromSelection(tool, { clear? })`                        | Turns the selected text into a highlight, an underline, a strikeout or a squiggly line, one per page. Resolves `{ annotations }`. [From the selected text](#from-the-selected-text)                                                              |
| `update(ref, changes, resources?)`                             | Changes only the fields you pass, and `null` removes one. New `resources` replace a stamp's drawing or an attachment's file. Resolves `{ annotation }`. [Change an annotation](#change-an-annotation)                                            |
| `delete(ref)`                                                  | Deletes an annotation, with its popup and its replies. [Delete an annotation](#delete-an-annotation)                                                                                                                                             |
| `reorder(refs, position)`                                      | Moves annotations to a new place in their page's drawing order: next to another annotation, or to the back or front. [Change the drawing order](#change-the-drawing-order)                                                                       |
| `export({ refs?, pages? })`, `import(bundle, options?)`        | Takes annotations out as a bundle, with the bytes they carry, or puts a bundle in. `import` resolves `{ annotations, refMap, dropped }`. [Export and import](saving#export-and-import)                                                           |
| `downloadResource(ref, role)`                                  | Downloads a stamp's drawing (`'appearance'`) or an attachment's file (`'file'`), ready to pass back to `create()`. [Copy an annotation](#copy-an-annotation)                                                                                     |
| `links.get(ref)`, `links.set(ref, target)`, `links.clear(ref)` | Where an annotation links to, such as a page or a website: read it, set it, or remove the link                                                                                                                                                   |
| `refresh()`                                                    | Reads every annotation from the engine again. Changes arrive by themselves, so you rarely need it.                                                                                                                                               |
| `getSettings()`, `updateSettings(changes)`, `resetSettings()`  | Reads `snap`, `chrome` and `afterCreate`, changes them while the app runs, or goes back to what you registered. In a component, use `useAnnotationSettings()`. [Settings](../annotations#settings)                                               |
| `canRead()`, `canCreate()`, `canUpdate(ref)`, `canDelete(ref)` | Whether the user may see, create, change or delete annotations. The checks that take a `ref` answer for that annotation. [Permissions](#permissions)                                                                                             |

Every method that returns a promise also takes a `signal` in its options, to cancel it.

## Permissions

| Permission           | What it allows     | Check                       |
| :------------------- | :----------------- | :-------------------------- |
| `doc.annotate.read`  | Seeing annotations | `annotation.canRead()`      |
| `annotations:create` | Creating them      | `annotation.canCreate()`    |
| `annotations:update` | Changing them      | `annotation.canUpdate(ref)` |
| `annotations:delete` | Deleting them      | `annotation.canDelete(ref)` |

A permission can end in `:self`, the user's own annotations, or `:group`, their group's.
`annotations:update:self` lets someone change what they made and only look at the rest, so the checks
that take a `ref` answer per annotation. They also follow the annotation's own flags: for a `locked`
one, `canDelete(ref)` is false, so your UI offers no delete button, even though your own code may still
delete it (see [Locked annotations](#locked-annotations)). More on
[Permissions](../concepts/permissions).

## Good to know

### Locked annotations

The `locked` flag stops people from moving, resizing or deleting an annotation, and
`lockedContents` stops them from changing its text. These flags are for the people using your
app: your own code can still change a locked annotation, as the engine can. To stop the code
too, check the flags first.

### What a copy can't carry

A few annotations can't be made again from their read, and `create()` refuses them with
`invalid-input`:

- a link whose action the engine can't create, such as one that runs JavaScript;
- a popup whose annotation is gone;
- a value another app wrote that the engine can't write, such as a beveled border;
- a kind the engine doesn't know (`subtype: 'unsupported'`).

[Import](saving#export-and-import) leaves these out and tells you which ones, and why.

### Changes other people make

On a shared document, other people's changes arrive as events and show up in `list()` by
themselves. See [Events, saving & sharing](saving).
