---
title: "Footnotes"
description: "Add Word-like footnotes to Tiptap Pages — per-page footnote areas, automatic numbering, in-place editing, and DOCX round-trip."
canonical_url: "https://tiptap.dev/docs/pages/core-concepts/footnotes"
---

# Footnotes

Add Word-like footnotes to Tiptap Pages — per-page footnote areas, automatic numbering, in-place editing, and DOCX round-trip.

Footnotes let your readers annotate text with numbered notes that render at the bottom of the page — right above the footer, exactly like in Microsoft Word. Each footnote is anchored to a superscript reference marker in the body text. The footnotes area takes up part of the page's available space, so the more footnotes a page has, the less room remains for body content.

Footnotes always stay with their reference: if editing pushes the referencing text onto another page, its footnote moves to that page's footnotes area automatically.

> **Imported from DOCX:**
>
> When you import a `.docx` file with the [DOCX import
> extension](https://tiptap.dev/docs/conversion/import/docx/editor-extension.md#footnotes--endnotes), footnotes from the
> document are auto-applied to Pages — references, content, and numbering. No extra wiring required.

> **Interactive demo:** [PagesFootnotes](https://embed-pro.tiptap.dev/preview/Extensions/PagesFootnotes)

## Enabling footnotes

Footnotes are disabled by default. Turn them on through the `footnotes` options group:

```js
import { Pages } from '@tiptap-pro/extension-pages'

Pages.configure({
  pageFormat: 'A4',
  footer: 'Page {page} of {total}',
  footnotes: {
    enabled: true,
  },
})
```

That's all you need — the footnote reference node is registered automatically, and the per-page footnote areas render as soon as the document contains footnotes.

## How footnotes behave

The mental model matches Microsoft Word:

- **One marker, one note.** Each footnote is a superscript number in the body text, paired with a numbered entry in the footnotes area of the page that contains the marker.
- **Footnotes live above the footer.** Each page renders its own footnotes area with a short separator rule, between the body content and the page footer.
- **Footnotes consume page space.** The area grows with its content and the body content area shrinks accordingly — adding footnotes to a full page pushes body text onto the next page.
- **Footnotes follow their references.** The area shows exactly the footnotes whose markers sit on that page. Reflowing text across pages moves the footnotes along with it; you never manage footnote placement yourself.
- **Numbering is automatic and continuous.** Footnotes are numbered `1, 2, 3…` in document order. Inserting a footnote between two existing ones renumbers everything after it; deleting one closes the gap. Numbers are computed, never stored — they're always correct.

## Inserting a footnote

Call `insertFootnote()` to add a footnote at the current selection:

```js
editor.commands.insertFootnote()
```

This inserts the reference marker **after** the selection (selected text is kept, like in Word), creates an empty footnote, and opens the footnotes editor with the caret placed inside the new footnote so the user can type its content immediately.

```jsx
<button onClick={() => editor.commands.insertFootnote()}>Insert footnote</button>
```

## Editing footnotes

Users edit footnotes directly by double-clicking a page's footnotes area. This opens a fully featured Tiptap editor scoped to that page: it shows the footnotes that belong to the page, in numbered order, each editable like regular rich text. Double-clicking a specific footnote places the caret in that footnote.

Close the editor with Escape, the close button, or by double-clicking outside it. While a footnotes editor is open, the main document editor is temporarily non-editable — same behavior as the header and footer editors.

### Custom extensions

The footnotes editor defaults to `ConvertKit`. Pass your own stack through `footnotes.extensions` to keep the schema consistent with your main editor:

```ts
import { ConvertKit } from '@tiptap-pro/extension-convert-kit'

Pages.configure({
  footnotes: {
    enabled: true,
    extensions: [ConvertKit.configure({ table: false })],
  },
})
```

> **Collaboration uses the callback form:**
>
> `footnotes.extensions` also accepts a `(ctx) => Extensions` callback that receives the Y field
> name and Y.Doc the footnotes editor should bind to. Use this form when you're wiring
> collaboration — see [Footnotes and collaboration](#footnotes-and-collaboration) below.

### Active editor state

While a footnotes editor is open, the extension exposes it through storage — the same pattern as headers and footers, so a unified toolbar works across all three:

- `activeEditor` – The Tiptap Editor instance of the open footnotes editor (or `null`)
- `activeEditorType` – `'footnotes'` while editing footnotes (alongside the existing `'header'` / `'footer'` / `null` values)
- `activePageNumber` – The page whose footnotes are being edited
- `footnotesEditorOn` / `footnotesEditorOff` – Subscribe/unsubscribe to events on the footnotes editor

```tsx
useEffect(() => {
  if (!editor) return

  const syncActiveEditorState = () => {
    const { activeEditor, activeEditorType } = editor.storage.pages
    // activeEditorType === 'footnotes' while the footnotes editor is open
  }

  editor.on('update', syncActiveEditorState)
  return () => {
    editor.off('update', syncActiveEditorState)
  }
}, [editor])
```

For a complete custom-toolbar example that follows the focused editor, see [Page header and footer → Building a custom toolbar](https://tiptap.dev/docs/pages/core-concepts/page-header-footer.md#building-a-custom-toolbar) — adding footnotes support to it only requires handling the additional `'footnotes'` value of `activeEditorType`.

### Locking the footnotes

Set `footnotes.editable` to `false` to render footnotes without the double-click editing affordance, or toggle at runtime:

```js
Pages.configure({
  footnotes: { enabled: true, editable: false },
})

// Runtime
editor.commands.setFootnotesEditable(false) // lock
editor.commands.setFootnotesEditable(true) // unlock
```

When locked, footnotes still render normally — only editing is disabled. `openFootnoteEditor` returns `false`, double-click is inert, and an already-open footnotes editor is closed.

### Preventing close on double-click

Like headers and footers, you can keep the footnotes editor open when the user double-clicks outside it — useful when your own toolbar sits outside the editor:

```js
Pages.configure({
  footnotes: { enabled: true },
  onDblClickFootnotesPreventClose: (event) => {
    const toolbar = document.querySelector('.my-toolbar')
    return toolbar?.contains(event.target)
  },
})
```

The generic `onDblClickHeaderFooterPreventClose` callback acts as the fallback when the footnotes-specific one is not provided.

### Programmatic open and close

```js
// Open the footnotes editor for page 2
editor.commands.openFootnoteEditor({ pageNumber: 2 })

// Open it with the caret placed in a specific footnote
editor.commands.openFootnoteEditor({ pageNumber: 2, focusNoteId: '3' })

// Close it
editor.commands.closeFootnoteEditor()
```

`openFootnoteEditor` returns `false` when footnotes are disabled, locked, or the page has no footnotes.

## Deleting footnotes and cleaning up

Deleting a reference marker from the body immediately removes its footnote from the page — and renumbers the rest. The footnote's **content is retained** behind the scenes so a plain undo restores the marker together with its text. This deliberately diverges from Word (which deletes the content immediately) in favor of undo safety.

When you want to permanently discard content whose references are gone, call:

```js
editor.commands.cleanupOrphanFootnotes()
```

This returns `true` when at least one orphaned footnote was removed.

## Copy & paste

Copying text that contains a footnote marker and pasting it elsewhere duplicates the footnote, Word-style: the pasted marker gets its own footnote with a copy of the original content, and the numbering updates across the document. The two footnotes are independent from that point on.

## References without content

A reference marker always produces a visible footnote — even when no content exists for it yet (for example after `setContent()` with a document that contains markers but before any footnote content was provided). Such footnotes render as empty numbered entries, and opening the footnotes editor makes them immediately editable.

## Navigating from a marker

Clicking a footnote marker in the body scrolls the page so its footnote is visible — handy in long documents.

## Configuration

All footnote settings live in the `footnotes` options group:

```ts
Pages.configure({
  footnotes: {
    enabled: true, // master switch (default: false)
    extensions: [ConvertKit], // editor extensions (or a collab-aware callback)
    initialContent: undefined, // seed content, keyed by note id
    separator: true, // the short rule above the area (default: true)
    maxHeightRatio: 0.5, // max fraction of page content height (default: 0.5)
    editable: true, // double-click editing (default: true)
    accentColor: '#6366f1', // footnotes editor accent (defaults to accentColor)
  },
})
```

### Seeding initial content

`initialContent` accepts footnote content keyed by note id — each id matching the `noteId` attribute of a `footnoteReference` node in your document content. The values are Tiptap `JSONContent` documents:

```js
Pages.configure({
  footnotes: {
    enabled: true,
    initialContent: {
      1: {
        type: 'doc',
        content: [
          {
            type: 'paragraph',
            content: [{ type: 'text', text: 'The first footnote.' }],
          },
        ],
      },
    },
  },
})
```

This is the exact shape the [DOCX import REST API](https://tiptap.dev/docs/conversion/import/docx/rest-api.md) returns in its `footnotes` field, so server-imported documents can be seeded directly.

> **Collaboration:**
>
> `initialContent` only applies when collaboration is **not** active — in collaborative documents
> the shared document owns the footnote content. To load footnotes into a collaborative document as
> an explicit user action (e.g. after a DOCX import), use the `setFootnotes` command instead.

### Separator rule

Word draws a short horizontal rule between the body and the footnotes. It's on by default; disable it with `separator: false`.

### Maximum area height

`maxHeightRatio` caps how much of the page's content height the footnotes area may take (default `0.5`, i.e. half the page). When footnotes exceed the cap, the area clips. See [What not to expect](#what-not-to-expect) for the difference from Word here.

### Accent color

The footnotes editor uses the shared `accentColor` by default; override it with `footnotes.accentColor` or at runtime:

```js
editor.commands.setFootnotesAccentColor('#10b981')
```

The accent affects the marker color in the body, the caret, the editor toolbar border, and the "Footnotes – page N" label.

## Accessing footnote content

Footnote content is exposed on `editor.storage.pages`, keyed by note id:

```js
// Tiptap JSON per footnote — use this for persistence and DOCX export
const footnotesJSON = editor.storage.pages.footnotesJSON
// { '1': { type: 'doc', content: [...] }, 'fn-abc123': { ... } }

// Rendered HTML per footnote — matches what the page previews show
const footnotesHTML = editor.storage.pages.footnotesHTML

// Current numbering (note id → 1-based number)
const numbers = editor.storage.pages.footnoteNumbers
```

### Saving and restoring

As with headers and footers, persisting footnote content is your responsibility. Save `footnotesJSON` alongside your document, and restore it with the `setFootnotes` command after loading the content:

```js
// Save
await saveDocument({
  content: editor.getJSON(),
  footnotes: editor.storage.pages.footnotesJSON,
})

// Restore
editor.commands.setContent(savedDocument.content)
editor.commands.setFootnotes(savedDocument.footnotes)
```

`setFootnotes` replaces all footnote content from an id → document map. References in the body keep working because they're matched by id.

## DOCX import and export

Footnotes round-trip with Word documents out of the box:

- **Import** — with the [DOCX import editor extension](https://tiptap.dev/docs/conversion/import/docx/editor-extension.md#footnotes--endnotes), footnotes from the imported file are auto-applied: markers appear in the body, content lands in the page areas, numbering matches the source document. The import context also exposes the raw `footnotes` / `endnotes` data if you want to handle them yourself.
- **Export** — with the [DOCX export editor extension](https://tiptap.dev/docs/conversion/export/docx/editor-extension.md), footnote content is auto-extracted from Pages and written as real Word footnotes: markers become live footnote references, and Word renders and renumbers them natively.

> **Interactive demo:** [PagesFootnotesImportExportDocx](https://embed-pro.tiptap.dev/preview/Extensions/PagesFootnotesImportExportDocx)

No configuration is needed on either side — install the import/export extensions next to Pages and footnotes are included.

## Footnotes and collaboration

Footnotes participate in collaboration alongside the main document: concurrent users see each other's footnote edits live, and inserting or deleting footnotes converges across clients, including the numbering.

To opt in, pass `footnotes.extensions` as a **callback** that attaches a `Collaboration` extension to the footnotes editor — the same pattern headers and footers use:

```ts
Pages.configure({
  footnotes: {
    enabled: true,
    extensions: (ctx) => {
      const base = [ConvertKit.configure({ undoRedo: false })]
      if (!ctx.isCollaborative || !ctx.ydoc) {
        return base
      }
      return [...base, Collaboration.configure({ document: ctx.ydoc, field: ctx.field })]
    },
  },
})
```

The callback receives the pre-computed Y field name (`ctx.field`) and the parent editor's Y.Doc (`ctx.ydoc`). See [Adding collaboration to Pages](https://tiptap.dev/docs/pages/guides/collaboration-with-pages.md) for the full collaboration setup, including the equivalent wiring for headers and footers.

> **Interactive demo:** [PagesFootnotesCollaboration](https://embed-pro.tiptap.dev/preview/Extensions/PagesFootnotesCollaboration)

## What to expect

- Word-like placement: footnotes at the bottom of the page that contains their marker, above the footer, with a separator rule.
- Automatic, continuous numbering that updates on every insert, delete, paste, and reflow.
- The page body shrinks as footnotes grow — pagination accounts for footnote space.
- Rich-text footnote content with the extensions you configure.
- Identical look between the rendered footnotes and the editing view — typography, numbering, and spacing match, including empty paragraphs used as vertical spacing.
- DOCX round-trip with no extra configuration.

## What not to expect

- **No footnote continuation across pages.** When a page's footnotes exceed `maxHeightRatio`, the area clips instead of continuing the overflow on the next page like Word does.
- **Endnotes are not rendered.** The DOCX import surfaces endnote data for your own handling, but Pages doesn't display or manage endnotes.
- **Decimal numbering only.** Footnotes number `1, 2, 3…` continuously through the document — per-page restarts and other formats (roman, letters, symbols) are not available yet.
- **Tables inside footnotes don't export.** They render in the editor, but Word's footnote format only accepts paragraphs, so tables are dropped on DOCX export.

> **Help us prioritise:**
>
> If one of these gaps blocks your use case, let us know — your feedback drives the roadmap.
>
> Share your use case with Tiptap

## Complete options reference

All options live under the `footnotes` key of `Pages.configure()` unless noted otherwise.

| Option                            | Type                                                | Default             | Description                                                                   |
| --------------------------------- | --------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------- |
| `enabled`                         | `boolean`                                           | `false`             | Master switch for the footnotes feature                                       |
| `extensions`                      | `Extensions \| (ctx) => Extensions`                 | `ConvertKit`        | Extensions for the footnotes editor. Use the callback form for collaboration. |
| `initialContent`                  | `Record<string, JSONContent>`                       | `undefined`         | Seed footnote content keyed by note id (non-collaborative documents only)     |
| `separator`                       | `boolean`                                           | `true`              | Render the Word-style separator rule above the area                           |
| `maxHeightRatio`                  | `number`                                            | `0.5`               | Maximum fraction of the page content height the area may occupy               |
| `editable`                        | `boolean`                                           | `true`              | Whether double-clicking the area opens the footnotes editor                   |
| `accentColor`                     | `string`                                            | `accentColor` value | Accent color for markers and the footnotes editor                             |
| `onDblClickFootnotesPreventClose` | `(event: MouseEvent) => boolean` (top-level option) | `undefined`         | Prevent closing the footnotes editor on double-click outside                  |

## Commands reference

| Command                   | Parameters                                     | Description                                                                   |
| ------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------- |
| `insertFootnote`          | none                                           | Insert a footnote at the selection and open its editor                        |
| `setFootnotes`            | `footnotes: Record<string, JSONContent>`       | Replace all footnote content from an id → document map                        |
| `openFootnoteEditor`      | `{ pageNumber: number, focusNoteId?: string }` | Open the footnotes editor for a page, optionally focusing a specific footnote |
| `closeFootnoteEditor`     | none                                           | Close the footnotes editor if open                                            |
| `setFootnotesEditable`    | `enabled: boolean`                             | Lock or unlock footnotes editing                                              |
| `cleanupOrphanFootnotes`  | none                                           | Permanently remove footnote content whose references are gone                 |
| `setFootnotesAccentColor` | `color: string`                                | Set the footnotes accent color                                                |

## Storage reference

Read these from `editor.storage.pages`:

| Property             | Type                                          | Description                                                          |
| -------------------- | --------------------------------------------- | -------------------------------------------------------------------- |
| `footnotesJSON`      | `Record<string, JSONContent>`                 | Footnote content per note id — use for persistence and DOCX export   |
| `footnotesHTML`      | `Record<string, string>`                      | Rendered HTML per note id, matching the page previews                |
| `footnoteNumbers`    | `Record<string, number>`                      | Current numbering (note id → 1-based number)                         |
| `footnotesEnabled`   | `boolean`                                     | Whether footnotes are enabled                                        |
| `editableFootnotes`  | `boolean`                                     | Whether editing is unlocked (read-only — use `setFootnotesEditable`) |
| `activeEditorType`   | `'header' \| 'footer' \| 'footnotes' \| null` | `'footnotes'` while the footnotes editor is open                     |
| `footnotesEditorOn`  | `Editor['on'] \| null`                        | Pre-bound subscriber for events on the footnotes editor              |
| `footnotesEditorOff` | `Editor['off'] \| null`                       | Pre-bound unsubscriber for the footnotes editor                      |
