Trailing Block inserts a required block when the last node at a target level is missing or has the wrong type. The standard registry editor plugins include TrailingBlockPlugin so full Plate editors always end with a paragraph. Single-block and single-line editors disable it because they intentionally keep one root block.
Add when users need a safe place to continue typing after blocks such as headings, tables, media, or columns.
TrailingBlockPluginimport { TrailingBlockPlugin } from 'platejs';
import { createPlateEditor } from 'platejs/react';
export const editor = createPlateEditor({
plugins: [TrailingBlockPlugin],
});import { TrailingBlockPlugin } from 'platejs';
import { createPlateEditor } from 'platejs/react';
export const editor = createPlateEditor({
plugins: [TrailingBlockPlugin],
});TrailingBlockPlugin defaults to the editor's paragraph type.
| Layer | Owner | What It Does |
|---|---|---|
TrailingBlockPlugin | platejs / @platejs/utils | Owns the correction that checks and inserts the trailing block. |
tx.nodes.last([], { level }) | Plite transaction | Finds the last node at the configured depth. |
NodeApi.matches(...) | @platejs/plite | Applies the optional last-node match. |
editor.ts plugins | Registry | Add TrailingBlockPlugin after editing plugins. |
SuggestionKit | Registry | Weakly adapts an installed trailing block without installing it. |
There is no dedicated trailing-block UI. The plugin is a normalizer.
Use type when the trailing block should be something other than the default paragraph.
import { ElementApi, PLUGINS, TrailingBlockPlugin } from 'platejs';
export const trailingBlockPlugin = TrailingBlockPlugin.configure(
({ editor }) => ({
initialState: {
type: editor.plugin(PLUGINS.paragraph).schema.type,
},
})
);import { ElementApi, PLUGINS, TrailingBlockPlugin } from 'platejs';
export const trailingBlockPlugin = TrailingBlockPlugin.configure(
({ editor }) => ({
initialState: {
type: editor.plugin(PLUGINS.paragraph).schema.type,
},
})
);The default is already editor.plugin(PLUGINS.paragraph).schema.type, so most editors can use the plugin directly.
Use match to limit insertion based on the current last block.
import { HeadingPlugin } from '@platejs/basic-nodes/react';
import { PLUGINS, TrailingBlockPlugin } from 'platejs';
export const trailingBlockPlugins = [
HeadingPlugin,
TrailingBlockPlugin.configure(({ editor }) => ({
initialState: {
match: (node) =>
ElementApi.isElement(node) &&
node.type === editor.plugin(PLUGINS.heading).schema.type,
type: editor.plugin(PLUGINS.paragraph).schema.type,
},
})),
] as const;import { HeadingPlugin } from '@platejs/basic-nodes/react';
import { PLUGINS, TrailingBlockPlugin } from 'platejs';
export const trailingBlockPlugins = [
HeadingPlugin,
TrailingBlockPlugin.configure(({ editor }) => ({
initialState: {
match: (node) =>
ElementApi.isElement(node) &&
node.type === editor.plugin(PLUGINS.heading).schema.type,
type: editor.plugin(PLUGINS.paragraph).schema.type,
},
})),
] as const;Install the referenced H1 capability beside the configured plugin. A trailing
paragraph is then inserted when the last block is an H1. Without match, any
last block whose type differs from the trailing type can trigger insertion.
level changes where the plugin looks for the last node.
level | Target |
|---|---|
0 | Last root block. |
1 | Last child inside the last root-level container. |
TrailingBlockPlugin.configure({
initialState: {
level: 1,
type: 'paragraph',
},
});TrailingBlockPlugin.configure({
initialState: {
level: 1,
type: 'paragraph',
},
});Use nested levels when a constrained container must always end with a text block.
initialState.insert lets another plugin wrap the generated insertion. The registry
suggestion kit contributes this option through override.plugins only when the
editor also installs TrailingBlockPlugin.
import { SUGGESTION_SKIP_TAG } from '@platejs/suggestion';
import { TrailingBlockPlugin } from 'platejs';
TrailingBlockPlugin.configure({
initialState: {
insert: (_editor, { insert, tx }) => {
tx.tags.add(SUGGESTION_SKIP_TAG);
insert();
},
},
});import { SUGGESTION_SKIP_TAG } from '@platejs/suggestion';
import { TrailingBlockPlugin } from 'platejs';
TrailingBlockPlugin.configure({
initialState: {
insert: (_editor, { insert, tx }) => {
tx.tags.add(SUGGESTION_SKIP_TAG);
insert();
},
},
});The callback receives the editor, active transaction tags, insertion path,
target type, and an insert() function. Call insert() exactly once unless you
are intentionally replacing the default insertion.
A direct TrailingBlockPlugin.configure(...) call is the strong owner and
takes precedence over the suggestion kit's weak contribution.
| Case | Result |
|---|---|
| Empty editor | Inserts a block at [0]. |
Last node already matches type | Falls through to the base normalizeNode. |
Last node has another type and matches initialState.match | Inserts the trailing block at PathApi.next(lastChildPath). |
Last node does not match initialState.match | Does not insert. |
The correction inserts { children: [{ text: '' }], type: trailingType }
through the active transaction.
| API | Package | Use |
|---|---|---|
TrailingBlockPlugin | platejs / @platejs/utils | Normalizer that ensures a trailing block exists. |
TrailingBlockPluginState.type | @platejs/utils | Block type to insert. Defaults to the editor paragraph type. |
TrailingBlockPluginState.level | @platejs/utils | Depth used by tx.nodes.last. Defaults to 0. |
TrailingBlockPluginState.insert | @platejs/utils | Custom wrapper around the generated insertion. |
TrailingBlockPluginState.match | @platejs/plite node match | Limits insertion to matching last nodes. |