Forced Layout is the NormalizeTypesPlugin pattern for pinning document positions to required node types. Use it for fixed slots such as "the first block is an H1." Use Trailing Block when the requirement is "the document always ends with a paragraph."
strictType for rewriting an existing node to a required type.type for inserting a missing node without rewriting an existing node.onError callback when insertion fails.enabled configuration.Add NormalizeTypesPlugin when specific paths must exist or hold a specific block type.
import { HeadingPlugin } from '@platejs/basic-nodes/react';
import { NormalizeTypesPlugin, PLUGINS } from 'platejs';
import { createPlateEditor } from 'platejs/react';
export const editor = createPlateEditor({
plugins: [
HeadingPlugin,
NormalizeTypesPlugin.configure(({ editor }) => ({
initialState: {
rules: [
{
path: [0],
strictType: editor.plugin(PLUGINS.heading).schema.type,
},
{
path: [1],
type: editor.plugin(PLUGINS.paragraph).schema.type,
},
],
},
})),
],
});import { HeadingPlugin } from '@platejs/basic-nodes/react';
import { NormalizeTypesPlugin, PLUGINS } from 'platejs';
import { createPlateEditor } from 'platejs/react';
export const editor = createPlateEditor({
plugins: [
HeadingPlugin,
NormalizeTypesPlugin.configure(({ editor }) => ({
initialState: {
rules: [
{
path: [0],
strictType: editor.plugin(PLUGINS.heading).schema.type,
},
{
path: [1],
type: editor.plugin(
This keeps the first block as an H1 and inserts a paragraph at path [1] when that node is missing.
| Layer | Owner | What It Does |
|---|---|---|
NormalizeTypesPlugin | platejs / @platejs/utils | Stores rules and onError, then overrides normalization. |
NormalizeTypesPlugin.corrections | @platejs/utils | Runs the configured path rules when root children change. |
tx.nodes.get(path) | Plite transaction | Reads the node at the configured path. |
tx.nodes.insert / tx.nodes.set | Plite transaction | Inserts missing nodes or corrects a strict type. |
| Playground demo | Registry example | Enables a first-block H1 rule when the playground id is forced-layout. |
There is no ForcedLayoutPlugin and no forced-layout. The public plugin is NormalizeTypesPlugin.
NormalizeTypesPlugin runs its root-level correction when root children change. It evaluates every configured rule in order during that correction.
| Rule Shape | Existing Node | Missing Node |
|---|---|---|
{ path, strictType } | If the node is an element with a different type, Plate sets its type to strictType and preserves children. | Plate inserts { type: strictType, children: [{ text: '' }] }. |
{ path, type } | Plate leaves the node alone. | Plate inserts { type, children: [{ text: '' }] }. |
Use strictType for required slots. Use type for optional slots that should be filled only when empty.
If inserting a missing node fails, the correction calls onError(error) and leaves the failed rule unchanged.
import { NormalizeTypesPlugin } from 'platejs';
export const requiredTitle = NormalizeTypesPlugin.configure({
initialState: {
onError: (error) => {
console.error(error);
},
rules: [{ path: [0], strictType: 'h1' }],
},
});import { NormalizeTypesPlugin } from 'platejs';
export const requiredTitle = NormalizeTypesPlugin.configure({
initialState: {
onError: (error) => {
console.error(error);
},
rules: [{ path: [0], strictType: 'h1' }],
},
});Keep onError small. A normalization callback should report or collect the failure, not mutate the same path again.
| Need | Use |
|---|---|
| First block must be a title | NormalizeTypesPlugin with strictType. |
| A missing slot should be inserted | NormalizeTypesPlugin with type. |
| Editor may only contain one root block | Single Block. |
| Editor must end with a paragraph | Trailing Block. |
| Pressing Enter should exit or reset a block | Plugin Rules. |
Forced layout is for absolute paths. It is not a schema engine for every possible nested node shape.
The registry playground demonstrates this pattern by enabling the plugin only for the forced-layout example id.
NormalizeTypesPlugin.configure({
enabled: id === 'forced-layout',
initialState: {
rules: [{ path: [0], strictType: 'h1' }],
},
});NormalizeTypesPlugin.configure({
enabled: id === 'forced-layout',
initialState: {
rules: [{ path: [0], strictType: 'h1' }],
},
});That example keeps the first playground block as an H1 while leaving the rest of the editor to normal Plate behavior.
| API | Package | Use |
|---|---|---|
NormalizeTypesPlugin | platejs / @platejs/utils | Path-based type normalization plugin. |
NormalizeTypesPluginState.rules | @platejs/utils | Ordered list of path rules. Defaults to []. |
Rule.path | @platejs/utils | Plite Path where the rule applies. |
Rule.strictType | @platejs/utils | Required type for an existing or missing node. |
Rule.type | @platejs/utils | Type for a missing node only. |
NormalizeTypesPluginState.onError | @platejs/utils | Called when inserting a missing node throws. |