---
title: "Links — Vue"
description: "Make the links in a PDF work: go to a page, open a website, and change how link areas look."
framework: "Vue"
source: "https://www.embedpdf.com/docs/headless/vue/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.vue`**

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

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

<template>
  <Viewer :engine="engine" :plugins="plugins">
    <Stage>
      <template #page>
        <RenderLayer />
        <LinkLayer />
      </template>
    </Stage>
  </Viewer>
</template>
```

**`App.vue`**

```vue
<script setup lang="ts">
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.vue';

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>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <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">
        <template #page>
          <RenderLayer />
          <LinkLayer />
        </template>
      </Stage>
    </DocumentGate>
  </Viewer>
</template>
```

**`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.vue`**

```vue
<!-- Made on load, since the document has none: a label the page shows, and a link over it. -->
<script setup lang="ts">
import { watch } from 'vue';
import { usePageList } from '@embedpdf/vue/runtime';
import { useAnnotation } from '@embedpdf/vue/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;
watch(
  pages,
  ([first, , third]) => {
    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,
      });
    void (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' },
      });
    })();
  },
  { immediate: true },
);
</script>

<template></template>
```

- **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><template #link>` lets you draw it
yourself, for example with an underline on hover:

**`custom-links.vue`**

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

<template>
  <LinkLayer>
    <template #link="{ link, native }">
      <span class="pdf-link" :title="link.target.kind === 'uri' ? link.target.uri : undefined">
        <component :is="native" />
      </span>
    </template>
  </LinkLayer>
</template>
```

**`App.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.vue';

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()) };
};

const shown = ref(true);
</script>

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

**`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.vue`**

```vue
<!-- Made on load, since the document has none: a label the page shows, and a link over it. -->
<script setup lang="ts">
import { watch } from 'vue';
import { usePageList } from '@embedpdf/vue/runtime';
import { useAnnotation } from '@embedpdf/vue/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;
watch(
  pages,
  ([first, , third]) => {
    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,
      });
    void (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' },
      });
    })();
  },
  { immediate: true },
);
</script>

<template></template>
```

`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.vue`**

```vue
<script setup lang="ts">
import { ref } from 'vue';
import { DocumentGate, Viewer } from '@embedpdf/vue/runtime';
import type { OpenInput } from '@embedpdf/vue/runtime';
import { Stage, stagePlugin } from '@embedpdf/vue/stage';
import { RenderLayer, renderPlugin } from '@embedpdf/vue/render';
import { interactionPlugin } from '@embedpdf/vue/interaction';
import { annotationPlugin } from '@embedpdf/vue/annotation';
import { LinkLayer, linkPlugin } from '@embedpdf/vue/link';
import { localEngine } from '@embedpdf/engine';
import AddLinks from './AddLinks.vue';
import LinkList from './LinkList.vue';

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()) };
};

// The list reads the links once they're made.
const ready = ref(false);
</script>

<template>
  <Viewer :engine="engine" :plugins="plugins" :initial-documents="[{ source: ebook }]">
    <DocumentGate>
      <template #fallback><p class="loading">Loading…</p></template>
      <AddLinks @added="ready = true" />
      <div class="layout">
        <LinkList :ready="ready" />
        <Stage class="stage">
          <template #page>
            <RenderLayer />
            <LinkLayer />
          </template>
        </Stage>
      </div>
    </DocumentGate>
  </Viewer>
</template>
```

**`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.vue`**

```vue
<!--
  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 setup lang="ts">
import { watch } from 'vue';
import { usePageList } from '@embedpdf/vue/runtime';
import { useAnnotation } from '@embedpdf/vue/annotation';

const emit = defineEmits<{ added: [] }>();

// 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;
watch(
  pages,
  ([first, , third]) => {
    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,
      });
    void (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)' },
      });
      emit('added');
    })();
  },
  { immediate: true },
);
</script>

<template></template>
```

**`LinkList.vue`**

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

const props = defineProps<{ ready: boolean }>();

const link = useLink();
const last = ref<string | null>(null);

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

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

<template>
  <aside class="panel">
    <h3 class="heading">Links on page 1</h3>
    <ul class="links">
      <li v-for="item in links" :key="item.id" 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" @click="link.activate(item)">Follow</button>
      </li>
    </ul>
    <p class="last"><code>onActivated</code> {{ last ?? 'not yet' }}</p>
  </aside>
</template>
```

## 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.vue`**

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

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

<template>
  <Stage>
    <template #page>
      <RenderLayer />
      <LinkLayer />
    </template>
  </Stage>
</template>
```

| 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).
