---
title: "Links — Svelte"
description: "Make the links in a PDF work: go to a page, open a website, and change how link areas look."
framework: "Svelte"
source: "https://www.embedpdf.com/docs/headless/svelte/viewing/links"
---

# Links

Most PDFs have links: a table of contents that jumps to a chapter, a footnote to a website. Register
`linkPlugin()` and put `<LinkLayer>` in each page, and they work:

**`link-layer.svelte`**

```svelte
<script lang="ts">
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { Viewer } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { engine } from './pdf';

  const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), linkPlugin()];
</script>

<Viewer {engine} {plugins}>
  <Stage>
    <RenderLayer />
    <LinkLayer />
  </Stage>
</Viewer>
```

**`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 { annotationPlugin } from '@embedpdf/svelte/annotation';
  import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
  import { localEngine } from '@embedpdf/engine';
  import AddLinks from './AddLinks.svelte';

  import '../basic.css';

  const engine = localEngine();
  // The annotation plugin is only here to make the links below.
  const plugins = [
    stagePlugin(),
    renderPlugin(),
    interactionPlugin(),
    annotationPlugin(),
    linkPlugin(),
  ];

  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}
    <AddLinks />
    <p class="hint">
      Two links at the top of the first page. Click one, or press Tab to reach it and Enter to
      follow it.
    </p>
    <Stage class="stage">
      <RenderLayer />
      <LinkLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`basic.css`**

```css
/* Colors */
:root {
  --muted: #5a6b92;
  --line: #e6eaf2;
  --canvas: #f7f9fc;
  --shadow: rgb(7 32 76 / 0.04);
}

/* What to try, above the pages */
.hint {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--muted);
}

/* 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);
}
```

**`AddLinks.svelte`**

```svelte
<!-- Made on load, since the document has none: a label the page shows, and a link over it. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useAnnotation } from '@embedpdf/svelte/annotation';

  // Where the two links go on the first page, in page coordinates.
  const TO_PAGE_3 = { x: 72, y: 24, width: 160, height: 28 };
  const TO_WEBSITE = { x: 250, y: 24, width: 190, height: 28 };

  const annotation = useAnnotation();
  const pages = usePageList();

  let added = false;
  $effect(() => {
    const [first, , third] = pages.current;
    if (!first || !third || added) return;
    added = true;
    const label = (text: string, box: typeof TO_PAGE_3) =>
      annotation.create(first.ref, {
        subtype: 'free-text',
        box,
        contents: text,
        fontSize: 13,
        fontColor: '#054fb3',
        interiorColor: '#e8f1ff',
        color: '#7db6ff',
        strokeWidth: 1,
      });
    untrack(async () => {
      await label('Go to page 3 →', TO_PAGE_3);
      await label('Open embedpdf.com ↗', TO_WEBSITE);
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_PAGE_3,
        target: { kind: 'goto', destination: { kind: 'fit', page: third.ref } },
      });
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_WEBSITE,
        target: { kind: 'uri', uri: 'https://www.embedpdf.com' },
      });
    });
  });
</script>
```

- **A link to a page** goes there, to the exact spot and zoom the link names.
- **A link to a website** opens in a new tab, after a safety check (see [Good to know](#good-to-know)).
- **A link with a PDF action**, such as a button that resets a form, runs the action when the
  [actions plugin](../documents/actions) is registered.

The pointer turns into a hand over a link, and a link can be reached with the Tab key and followed
with Enter.

## Change how links look

By default a link area is invisible, as in other PDF apps. `<LinkLayer>{#snippet link()}` lets you draw it
yourself, for example with an underline on hover:

**`custom-links.svelte`**

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

<LinkLayer>
  {#snippet link({ link, native })}
    <span class="pdf-link" title={link.target.kind === 'uri' ? link.target.uri : undefined}>
      {@render native()}
    </span>
  {/snippet}
</LinkLayer>
```

**`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 { annotationPlugin } from '@embedpdf/svelte/annotation';
  import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
  import { localEngine } from '@embedpdf/engine';
  import AddLinks from './AddLinks.svelte';

  import '../custom.css';

  const engine = localEngine();
  // The annotation plugin is only here to make the links below.
  const plugins = [
    stagePlugin(),
    renderPlugin(),
    interactionPlugin(),
    annotationPlugin(),
    linkPlugin(),
  ];

  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>

<script lang="ts">
  let shown = $state(true);
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <AddLinks />
    <div class="toolbar">
      <div class="segmented" role="group" aria-label="Link areas">
        <button type="button" aria-pressed={shown} onclick={() => (shown = true)}>
          Show link areas
        </button>
        <button type="button" aria-pressed={!shown} onclick={() => (shown = false)}>
          As the PDF has them
        </button>
      </div>
    </div>
    <Stage class="stage">
      <RenderLayer />
      <LinkLayer>
        {#snippet link({ link, native })}
          {#if shown}
            <!-- Wrapping `native` keeps what a click does. -->
            <span class="pdf-link" data-kind={link.target.kind}>{@render native()}</span>
          {:else}
            <!-- The layer's own, invisible area. -->
            {@render native()}
          {/if}
        {/snippet}
      </LinkLayer>
    </Stage>
  </DocumentGate>
</Viewer>
```

**`custom.css`**

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

/* Show or hide the link areas */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

/* 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;
}

/* Your look for a link: a tint, and an underline that grows on hover */
.pdf-link a {
  border-radius: 4px;
  background: rgb(8 118 253 / 0.08);
  box-shadow: inset 0 -2px 0 rgb(8 118 253 / 0.55);
  transition:
    background-color 0.15s ease,
    box-shadow 0.15s ease;
}

.pdf-link a:hover {
  background: rgb(8 118 253 / 0.16);
  box-shadow: inset 0 -3px 0 var(--mark);
}

/* A link to a website gets a different color */
.pdf-link[data-kind='uri'] a {
  background: rgb(22 163 74 / 0.08);
  box-shadow: inset 0 -2px 0 rgb(22 163 74 / 0.6);
}

.pdf-link[data-kind='uri'] a:hover {
  background: rgb(22 163 74 / 0.16);
}

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

/* 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);
}
```

**`AddLinks.svelte`**

```svelte
<!-- Made on load, since the document has none: a label the page shows, and a link over it. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useAnnotation } from '@embedpdf/svelte/annotation';

  // Where the two links go on the first page, in page coordinates.
  const TO_PAGE_3 = { x: 72, y: 24, width: 160, height: 28 };
  const TO_WEBSITE = { x: 250, y: 24, width: 190, height: 28 };

  const annotation = useAnnotation();
  const pages = usePageList();

  let added = false;
  $effect(() => {
    const [first, , third] = pages.current;
    if (!first || !third || added) return;
    added = true;
    const label = (text: string, box: typeof TO_PAGE_3) =>
      annotation.create(first.ref, {
        subtype: 'free-text',
        box,
        contents: text,
        fontSize: 13,
        fontColor: '#054fb3',
        interiorColor: '#e8f1ff',
        color: '#7db6ff',
        strokeWidth: 1,
      });
    untrack(async () => {
      await label('Go to page 3 →', TO_PAGE_3);
      await label('Open embedpdf.com ↗', TO_WEBSITE);
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_PAGE_3,
        target: { kind: 'goto', destination: { kind: 'fit', page: third.ref } },
      });
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_WEBSITE,
        target: { kind: 'uri', uri: 'https://www.embedpdf.com' },
      });
    });
  });
</script>
```

`native` is the layer's own clickable area: wrap it to keep what a click does.
For a link you don't want to change, show `native` alone.

## Read the links on a page

```ts
link.listLinks(page.ref); // every link on the page
link.getLinkAt(page.ref, { x: 120, y: 80 }); // the link at a point, or null
```

Each link has an `id`, its `bounds` on the page, and a `target`: where it goes.

| `target.kind`               | Goes to                                                                           |
| :-------------------------- | :-------------------------------------------------------------------------------- |
| `'goto'`                    | A page of this document: `target.destination`                                     |
| `'uri'`                     | A website or an email address: `target.uri`                                       |
| `'named'`                   | A viewer action, such as the next page                                            |
| `'javascript'`              | Runs the link's script, when the [actions plugin](../documents/actions) allows it |
| `'goto-remote'`, `'launch'` | Nowhere: see [Which links never run](#which-links-never-run)                      |

## Follow a link from code

`activate()` does what a click does:

```ts
const { outcome } = link.activate(someLink);
```

It returns what happened right away, so a website can still open in a new tab: `'revealed'` (the
view went to a page), `'uri'` (a website opened), or `'dispatched'` (the actions plugin runs the
link's action). A link it won't follow, such as one to another file, is `'reported'`.

**`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 { annotationPlugin } from '@embedpdf/svelte/annotation';
  import { LinkLayer, linkPlugin } from '@embedpdf/svelte/link';
  import { localEngine } from '@embedpdf/engine';
  import AddLinks from './AddLinks.svelte';
  import LinkList from './LinkList.svelte';

  import '../list.css';

  const engine = localEngine();
  // The annotation plugin is only here to make the links below.
  const plugins = [
    stagePlugin(),
    renderPlugin(),
    interactionPlugin(),
    annotationPlugin(),
    linkPlugin(),
  ];

  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>

<script lang="ts">
  // The list reads the links once they're made.
  let ready = $state(false);
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <AddLinks onAdded={() => (ready = true)} />
    <div class="layout">
      <LinkList {ready} />
      <Stage class="stage">
        <RenderLayer />
        <LinkLayer />
      </Stage>
    </div>
  </DocumentGate>
</Viewer>
```

**`list.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --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 list beside the pages; above them on a narrow screen */
.layout {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.panel {
  flex: 1 1 100%;
  min-width: 0;
}

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

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

/* One link: what it is, where it goes, where it sits, and a button to follow it */
.link {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 8px;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 8px 10px;
  box-shadow: 0 1px 2px var(--shadow);
}

.kind {
  font-family: ui-monospace, monospace;
  font-size: 11.5px;
  color: var(--accent);
}

.label {
  grid-column: 1;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.where {
  grid-column: 1;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.link .button {
  grid-column: 2;
  grid-row: 1 / span 3;
}

.last {
  margin: 10px 0 0;
  font-size: 12.5px;
  color: var(--ink);
}

.last code {
  margin-right: 6px;
  font-weight: 600;
  color: var(--accent);
}

.button {
  min-width: 38px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 550;
  line-height: 1.1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 11px;
  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:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

/* 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);
  flex: 1 1 100%;
}

@container (min-width: 620px) {
  .panel {
    flex: 0 0 240px;
  }

  .stage {
    flex: 1 1 0;
    min-width: 0;
  }
}

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

**`AddLinks.svelte`**

```svelte
<!--
  Made on load, since the document has none: a label the page shows, and a link over it. The
  third one is a `javascript:` address, which a link never opens.
-->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useAnnotation } from '@embedpdf/svelte/annotation';

  let { onAdded }: { onAdded: () => void } = $props();

  // Where the links go on the first page, in page coordinates.
  const TO_PAGE_3 = { x: 72, y: 24, width: 160, height: 28 };
  const TO_WEBSITE = { x: 250, y: 24, width: 190, height: 28 };
  const TO_SCRIPT = { x: 458, y: 24, width: 82, height: 28 };

  const annotation = useAnnotation();
  const pages = usePageList();

  let added = false;
  $effect(() => {
    const [first, , third] = pages.current;
    if (!first || !third || added) return;
    added = true;
    const label = (text: string, box: typeof TO_PAGE_3) =>
      annotation.create(first.ref, {
        subtype: 'free-text',
        box,
        contents: text,
        fontSize: 13,
        fontColor: '#054fb3',
        interiorColor: '#e8f1ff',
        color: '#7db6ff',
        strokeWidth: 1,
      });
    untrack(async () => {
      await label('Go to page 3 →', TO_PAGE_3);
      await label('Open embedpdf.com ↗', TO_WEBSITE);
      await label('A script', TO_SCRIPT);
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_PAGE_3,
        target: { kind: 'goto', destination: { kind: 'fit', page: third.ref } },
      });
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_WEBSITE,
        target: { kind: 'uri', uri: 'https://www.embedpdf.com' },
      });
      await annotation.create(first.ref, {
        subtype: 'link',
        rect: TO_SCRIPT,
        target: { kind: 'uri', uri: 'javascript:alert(1)' },
      });
      onAdded();
    });
  });
</script>
```

**`LinkList.svelte`**

```svelte
<!-- The first page's links, read from the plugin, each with a button that follows it. -->
<script lang="ts">
  import { useLink, useLinkEvent } from '@embedpdf/svelte/link';

  let { ready }: { ready: boolean } = $props();

  const link = useLink();
  let last = $state<string | null>(null);

  // Every way of following a link ends up here: a click, a key, or code.
  useLinkEvent(
    (plugin) => plugin.onActivated,
    ({ target, activation }) => (last = `${target.kind} → ${activation.outcome}`),
  );

  const links = $derived(ready ? link.listLinks(0) : []);
</script>

<aside class="panel">
  <h3 class="heading">Links on page 1</h3>
  <ul class="links">
    {#each links as item (item.id)}
      <li class="link">
        <span class="kind">{item.target.kind}</span>
        <span class="label">{link.getLabel(item)}</span>
        <span class="where">
          at {Math.round(item.bounds.x)}, {Math.round(item.bounds.y)}
        </span>
        <button type="button" class="button" onclick={() => link.activate(item)}>Follow</button>
      </li>
    {/each}
  </ul>
  <p class="last">
    <code>onActivated</code>
    {last ?? 'not yet'}
  </p>
</aside>
```

## Methods

Everything `useLink()` gives you.

| Method                              | What it does                                                                                                                          |
| :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| `listLinks(page)`                   | Every link on a page, once the page's links are read. [Read the links on a page](#read-the-links-on-a-page)                           |
| `getLink(page, id)`                 | One link by its `id`, or `null`                                                                                                       |
| `getLinkAt(page, point)`            | The link at a point on the page, the smallest when links overlap, or `null`                                                           |
| `listAllLinks()`                    | Every link in the document, reading each page's links as needed                                                                       |
| `ensureLoaded(page)`                | Reads a page's links if they aren't read yet. `<LinkLayer>` does this for every page it shows.                                        |
| `isLoaded(page)`, `getStatus(page)` | Whether a page's links are read, and in detail: `'idle'`, `'loading'`, `'ready'`, `'error'` or `'forbidden'`                          |
| `activate(link)`                    | Follows a link, or a link's `target`, as a click does, and returns what happened. [Follow a link from code](#follow-a-link-from-code) |
| `activateAt(page, point)`           | Follows the link at a point on the page. Returns `null` when there's no link there.                                                   |
| `resolve(target)`                   | What following a target would do, without doing it: `{ kind }` is `'destination'`, `'uri'`, `'named'` or `'reported'`                 |
| `getLabel(link)`                    | A short label for a tooltip: the web address, or where the link goes                                                                  |

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

## Events

**`link-analytics.svelte`**

```svelte
<script lang="ts">
  import { LinkLayer, useLinkEvent } from '@embedpdf/svelte/link';
  import { RenderLayer } from '@embedpdf/svelte/render';
  import { Stage } from '@embedpdf/svelte/stage';
  import { analytics } from './analytics';

  useLinkEvent(
    (link) => link.onActivated,
    ({ target }) => analytics.track('link_followed', { kind: target.kind }),
  );
</script>

<Stage>
  <RenderLayer />
  <LinkLayer />
</Stage>
```

| Event         | Fires when                                             | Carries                |
| :------------ | :----------------------------------------------------- | :--------------------- |
| `onActivated` | Someone followed a link, by click, key or code         | `target`, `activation` |
| `onLoaded`    | A page's links were read, or read again after a change | `page`                 |

## Good to know

### Which links never run

A website link opens only with `http`, `https`, `mailto` or `tel`. An address without one opens on
`https` when it starts with `//` or `www.`; any other, such as `/help` or `page2.html`, is relative
to the document and opens nothing, so a link can never open a page of your own app. Links that
would start a program or open another file on the user's computer are never followed, and a link's
JavaScript runs only when the [actions plugin](../documents/actions) has JavaScript turned on.

### Links while editing annotations

Links work while the `pointer` or `pan` tool is active. With the annotation plugin's `link` tool,
the layer steps aside and links become annotations you can move, resize and retarget; see
[Annotations](../annotations).

### Making links

Links are annotations: create them with the annotation plugin, or with the engine; see
[Links](https://www.embedpdf.com/docs/engine/annotations/links).
