From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Stream
    • Copilot
  • Discussion
    • Comments
    • Suggestion
    • Basic Blocks
      • Blockquote
      • Heading
      • Horizontal Rule
    • Callout
    • Code Block
    • Column
    • Date
    • Equation
    • Link
    • List Classic
    • Media
    • MentionElement
    • Table
    • Table of Contents
    • Footnote
    • Toggle
  • Marks
    • Bold
    • Italic
    • Underline
    • Code
    • Highlight
    • Keyboard Input
    • Strikethrough
    • Subscript
    • Superscript
      • Font
      • Line Height
      • Text Align
    • Indent
    • List
      • Exit Break
      • Single Block
      • Trailing Block
    • Autoformat
    • Block Menu
    • Block Placeholder
    • Combobox
      • Emoji
      • MentionElement
      • Slash Command
    • Cursor Overlay
    • Drag & Drop
    • Navigation Feedback
    • Tabbable
    • Toolbar
    • Yjs
    • Multi SelectEditor
    • CSV
    • DOCX
    • HTML
    • Markdown

Footnote

PreviousNext

Parse and edit GFM footnote references and definitions as dedicated nodes.

MarkdownNavigation Feedback
Loading…
Table of ContentsToggle

On This Page

FeaturesKit UsageInstallationAdd KitManual UsageInstallationAdd PluginsInsert a FootnoteRepair an Unresolved ReferenceNavigate Between Reference and DefinitionHandle Duplicate DefinitionsCustomize RenderingPluginsFootnotePluginFootnoteDefinitionPluginFootnoteInputPluginReadseditor.read.footnote.definitioneditor.read.footnote.definitionseditor.read.footnote.definitionTexteditor.read.footnote.referenceseditor.read.footnote.refseditor.read.footnote.nextRefeditor.read.footnote.isResolvededitor.read.footnote.duplicateDefinitionseditor.read.footnote.duplicateRefseditor.read.footnote.hasDuplicateDefinitionseditor.read.footnote.isDuplicateDefinitionUpdateseditor.update.footnote.inserteditor.update.footnote.createDefinitioneditor.update.footnote.focusDefinitioneditor.update.footnote.focusReferenceeditor.update.footnote.normalizeDuplicateDefinitionRelated Docs
Build your editor
Production-ready AI template and reusable components.
Get all-access

Footnote turns GFM footnote markup ([^1] references and [^1]: text definitions) into dedicated Plate nodes you can insert, repair, and jump between. The reference is an inline void <sup>; the definition is a block at the end of the document. Paired with MarkdownPlugin and remark-gfm, references and definitions round-trip as real footnote markdown instead of fallback text.

Features

  • GFM-compatible footnote references and definitions as dedicated Plate nodes.
  • One-transform insertion with automatic numeric ref allocation.
  • [^ inline combobox for insertion from the default UI kit.
  • Recreate a missing definition from an unresolved reference without duplicating.
  • Keep the first duplicate definition canonical; renumber later duplicates on demand.
  • Navigation helpers that jump between reference and definition with a landed-target flash.
Report an issue

Kit Usage

Installation

The fastest way to add footnote-aware markdown is with the MarkdownKit, which includes MarkdownPlugin, the footnote plugins wired for the default markdown profile, and works with Plate UI.

import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
import { PLUGINS } from 'platejs';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
 
export const MarkdownKit = [
  MarkdownPlugin.configure(({ editor }) => {
    const comment = editor.plugin(PLUGINS.comment);
    const suggestion = editor.plugin(PLUGINS.suggestion);
    const plainMarks: string[] = [];
 
    if (suggestion.installed) {
      plainMarks.push(suggestion.schema.key);
    }
    if (comment.installed) {
      plainMarks.push(comment.schema.key);
    }
 
    return {
      initialState: {
        plainMarks,
        remarkPlugins: [
          remarkMath,
          remarkGfm,
          remarkEmoji,
          remarkMdx,
          remarkMention,
        ],
      },
    };
  }),
];
import { MarkdownPlugin, remarkMdx, remarkMention } from '@platejs/markdown';
import { PLUGINS } from 'platejs';
import remarkEmoji from 'remark-emoji';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
 
export const MarkdownKit = [
  MarkdownPlugin.configure(({ editor }) => {
    const comment = editor.plugin(PLUGINS.comment);
    const suggestion = editor.plugin(PLUGINS.suggestion);
    const plainMarks: string[] =





















Add Kit

import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/markdown';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    ...MarkdownKit,
  ],
});
import { createPlateEditor } from 'platejs/react';
import { MarkdownKit } from '@/components/editor/markdown';
 
const editor = createPlateEditor




Manual Usage

Installation

pnpm add @platejs/footnote @platejs/markdown remark-gfm
pnpm add @platejs/footnote @platejs/markdown remark-gfm

Add Plugins

Add the inline reference and block definition. FootnotePlugin installs its combobox input as a required dependency; pair the footnote plugins with MarkdownPlugin and remark-gfm so [^1] round-trips correctly.

import {
  FootnoteDefinitionPlugin,
  FootnotePlugin,
} from '@platejs/footnote/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
import remarkGfm from 'remark-gfm';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    FootnotePlugin,
    FootnoteDefinitionPlugin,
    MarkdownPlugin.configure({
      initialState: {
        remarkPlugins: [remarkGfm],
      },
    }),
  ],
});

FootnoteInputPlugin is a required dependency of FootnotePlugin. Add a complete input descriptor to the same array only when you need to replace its default component or configuration.

Insert a Footnote

Call tx.footnote.insert at the current selection. It inserts the reference, creates a matching definition at the end of the document, and moves the caret into the definition body so the reader can start writing:

editor.update((tx) => tx.footnote.insert());
editor.update((tx) => tx.footnote.insert());

When the selection is expanded, the expanded fragment seeds the definition body so you can select text and "footnote-ify" it in one shot.

Pass focusDefinition: false when the reference should stay inline (for example, inside a larger template):

editor.update((tx) => tx.footnote.insert({ focusDefinition: false }));
editor.update((tx) => tx.footnote.insert({ focusDefinition: false }));

Pass ref to reuse an existing ref; the transform skips creating a duplicate definition when one already exists.

Repair an Unresolved Reference

When a reference points at an ref with no definition (e.g. pasted from elsewhere), use tx.footnote.createDefinition to create just the definition — without inserting another reference:

editor.update((tx) => tx.footnote.createDefinition({ ref: '3' }));
editor.update((tx) => tx.footnote.createDefinition({ ref: '3' }));

Pass focus: false when you want to leave the caret where it was:

editor.update((tx) =>
  tx.footnote.createDefinition({ focus: false, ref: '3' })
);
editor.update((tx) =>
  tx.footnote.createDefinition({ focus: false, ref: '3' })
);

Navigate Between Reference and Definition

tx.footnote.focusDefinition and tx.footnote.focusReference jump the selection, scroll the target into view, and flash it through Navigation Feedback. No extra wiring needed:

editor.update((tx) => tx.footnote.focusDefinition({ ref: '3' }));
editor.update((tx) => tx.footnote.focusReference({ ref: '3' }));
editor.update((tx) => tx.footnote.focusDefinition({ ref: '3' }));
editor.update((tx) => tx.footnote.focusReference({ ref: '3' }));

When a single definition is pointed at by multiple references, pass index to pick which one to land on:

editor.update((tx) =>
  tx.footnote.focusReference({ ref: '3', index: 1 })
);
editor.update((tx) =>
  tx.footnote.focusReference({ ref: '3', index: 1 })
);

Both transaction commands return false when the ref doesn't resolve, so you can branch on stale links without throwing.

Handle Duplicate Definitions

Two definitions with the same ref is a resolvable edit state, not an error. The first definition in document order stays canonical; later ones are flagged as duplicates. Renumber a later duplicate with:

let nextRefentifier: string | undefined;
 
editor.update((tx) => {
  nextRefentifier = tx.footnote.normalizeDuplicateDefinition({
    path: duplicatePath,
  });
});
let nextRefentifier: string | undefined;
 
editor.update((tx) => {
  nextRefentifier = tx.footnote.normalizeDuplicateDefinition({


The transform returns the newly assigned ref string on success, or false when the path isn't a duplicate definition or the requested ref is already taken. Pass ref to target a specific free ref instead of the next available one.

Customize Rendering

Swap in your own React components with component:

import {
  FootnoteDefinitionPlugin,
  FootnotePlugin,
} from '@platejs/footnote/react';
import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [
    FootnotePlugin.configure({ component: MyFootnoteReference }),
    FootnoteDefinitionPlugin.configure({ component: MyFootnoteDefinition }),
  ],
});
import {
  FootnoteDefinitionPlugin,









The package owns node semantics, ref allocation, and navigation helpers. App-level surfaces — hover previews, the [^ combobox, slash-command entries, toolbar buttons — are built on top of the transforms and API methods below.

Plugins

FootnotePlugin

Inline void node rendered as <sup>. Owns the [^ combobox trigger, ref registry, navigation transforms, and query API. Requires FootnoteInputPlugin.

Options

    Character that opens the footnote combobox.

    • Default: '^'

    Only trigger when the previous character matches. The default requires [ so bare ^ in prose doesn't open the combobox.

    • Default: /^\[$/

    Factory for the node inserted when the combobox opens. Defaults to a footnoteInput element.

    Extra predicate gating the combobox. Return false to suppress triggering at the current selection.

FootnoteDefinitionPlugin

Block node for footnote definitions. Lives at the bottom of the document and carries the ref + body content.

FootnoteInputPlugin

Inline void used as the live combobox input while the reader is typing [^…. Installed as a required dependency of FootnotePlugin; add it directly only to replace its default configuration or component.

Reads

All read methods hang off editor.read.footnote and evaluate the active snapshot directly.

editor.read.footnote.definition

Get the canonical (first-in-document-order) definition entry for an ref.

Parameters

    Footnote ref.

Returns

    Canonical definition entry, or undefined when nothing matches.

editor.read.footnote.definitions

Get every definition entry that shares an ref, in document order. When duplicates exist, the first entry is canonical; later entries are duplicates.

Parameters

    Footnote ref.

Returns

    Definition entries in document order.

editor.read.footnote.definitionText

Get the plain-text content of the canonical definition. Ideal for hover previews — reads straight from live definition nodes, no copied state.

Parameters

    Footnote ref.

Returns

    Definition text, or undefined when no definition exists.

editor.read.footnote.references

Get every reference entry that points at an ref, in document order.

Parameters

    Footnote ref.

Returns

    Reference entries in document order.

editor.read.footnote.refs

List every ref that has at least one definition, in document order.

Returns

    Defined refs.

editor.read.footnote.nextRef

Compute the next free numeric ref. Used by tx.footnote.insert when the caller doesn't supply one.

Returns

    Next free ref (e.g. '1', '2').

editor.read.footnote.isResolved

Check whether an ref has at least one definition.

Parameters

    Footnote ref.

Returns

    true when at least one definition exists for the ref.

editor.read.footnote.duplicateDefinitions

Get every non-canonical definition entry for an ref — that is, every definition after the first in document order.

Parameters

    Footnote ref.

Returns

    Definition entries past the canonical one.

editor.read.footnote.duplicateRefs

List every ref that has more than one definition.

Returns

    Refs with duplicate definitions.

editor.read.footnote.hasDuplicateDefinitions

Check whether an ref has more than one definition.

Parameters

    Footnote ref.

Returns

    true when two or more definitions share the ref.

editor.read.footnote.isDuplicateDefinition

Check whether a given definition path is a later duplicate (not the canonical one).

Parameters

    Path of the definition to check.

Returns

    true when the node at path is a footnote definition past the canonical one.

Updates

editor.update.footnote.insert

Insert a footnote reference at the current selection, create a matching definition if one doesn't already exist, and focus the definition body.

When the selection is expanded, the expanded fragment seeds the new definition body so you can convert selected prose into a footnote in one call.

Parameters

    Standard reference insertion options such as at and select.

editor.update.footnote.createDefinition

Create the missing definition for an existing ref without inserting another reference. Returns the path of the definition — the newly created one, or the existing one when the ref already resolves.

Parameters

    Ref to create a definition for.

    Focus the definition body after creation.

    • Default: true

Returns

    Path of the resolved definition.

editor.update.footnote.focusDefinition

Jump the selection into the canonical definition body, scroll it into view, and flash it through Navigation Feedback.

Parameters

    Footnote ref.

Returns

    false when no definition resolves, true otherwise.

editor.update.footnote.focusReference

Jump the selection to the matching reference, scroll it into view, and flash it through Navigation Feedback.

Parameters

    Footnote ref.

    Pick a specific reference when the definition is pointed at by several. Indexed by document order.

    • Default: 0

Returns

    false when the reference doesn't resolve, true otherwise.

editor.update.footnote.normalizeDuplicateDefinition

Renumber a later duplicate definition so the canonical definition stays intact. Pass the path of the duplicate; optionally pass a specific ref to target, otherwise the transform picks editor.read.footnote.nextRef().

Parameters

    Path of the duplicate definition to renumber.

    Target ref. Must be free. Defaults to editor.read.footnote.nextRef().

Returns

    The assigned ref on success, or false when the path isn't a duplicate definition or the target ref is already taken.

Related Docs

  • Markdown
  • Navigation Feedback
  • Editor Methods
[];
if (suggestion.installed) {
plainMarks.push(suggestion.schema.key);
}
if (comment.installed) {
plainMarks.push(comment.schema.key);
}
return {
initialState: {
plainMarks,
remarkPlugins: [
remarkMath,
remarkGfm,
remarkEmoji,
remarkMdx,
remarkMention,
],
},
};
}),
];
({
plugins: [
// ...otherPlugins,
...MarkdownKit,
],
});
import {
  FootnoteDefinitionPlugin,
  FootnotePlugin,
} from '@platejs/footnote/react';
import { MarkdownPlugin } from '@platejs/markdown';
import { createPlateEditor } from 'platejs/react';
import remarkGfm from 'remark-gfm';
 
const editor = createPlateEditor({
  plugins: [
    // ...otherPlugins,
    FootnotePlugin,
    FootnoteDefinitionPlugin,
    MarkdownPlugin.configure({
      initialState: {
        remarkPlugins: [remarkGfm],
      },
    }),
  ],
});
path: duplicatePath,
});
});
FootnotePlugin,
} from '@platejs/footnote/react';
import { createPlateEditor } from 'platejs/react';
const editor = createPlateEditor({
plugins: [
FootnotePlugin.configure({ component: MyFootnoteReference }),
FootnoteDefinitionPlugin.configure({ component: MyFootnoteDefinition }),
],
});