From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • Plugin
    • Plugin Methods
    • Plugin Shortcuts
    • Plugin Context
    • Plugin Components
    • Plugin Rules
    • Editing Behavior
    • Plugin Input Rules
  • Editor
    • Editor Methods
    • Controlled Value
  • Performance
  • Static Rendering
  • HTML
  • Markdown
  • Form
  • TypeScript
  • Debugging
  • Unit Testing
  • Browser
  • Troubleshooting

Editor Configuration

PreviousNext

Learn how to configure and customize the Plate editor.

Create the editor with its initial document and plugin tuple. Configure editor-wide runtime policy here; configure feature behavior on its owning plugin.

Basic Editor Configuration

To create a basic Plate editor, you can use the createPlateEditor function, or usePlateEditor in a React component:

import { createPlateEditor } from 'platejs/react';
 
const editor = createPlateEditor({
  plugins: [HeadingPlugin],
});
Plugin Input RulesDocument Model

On This Page

Basic Editor ConfigurationInitial ValueSchema IdentityCustom Primary RootDocument MigrationsRemote Initial ValueAdding PluginsExact Generated Editor TypesMax LengthAdvanced ConfigurationEditor IDNode IdentityNavigation FeedbackConfigurationDisabling Navigation FeedbackNormalizationAuto-selectionComponent OverridesConfigure PluginsWeak Peer OverridesReplace Dependency DefaultsTyped EditorPlugins TypeValue Type
Build your editor
Production-ready AI template and reusable components.
Get all-access
import { createPlateEditor } from 'platejs/react'; const editor = createPlateEditor({ plugins: [HeadingPlugin], });

Initial Value

Set the initial content of the editor:

const editor = createPlateEditor({
  initialValue: [
    {
      type: 'paragraph',
      children: [{ text: 'Hello, Plate!' }],
    },
  ],
});
const editor = createPlateEditor({
  initialValue: [
    {
      type: 'paragraph',
      children: [{ text: 'Hello, Plate!' }],
    },
  ],
});

Pass an EditorDocumentValue with children, roots, and persisted meta when one editor owns content roots or other named regions. Plate publishes the same complete shape through onValueChange.

Use a synchronous initializer when decoding requires the compiled plugin model:

const editor = createPlateEditor({
  plugins: [HtmlPlugin, BoldPlugin, ItalicPlugin],
  initialValue: ({ editor }) =>
    editor.api.html.deserialize({
      element: '<p>This is <b>bold</b> and <i>italic</i> text!</p>',
    }),
});
const editor = createPlateEditor({
  plugins: [HtmlPlugin, BoldPlugin, ItalicPlugin],
  initialValue: ({ editor }) =>
    editor.api.html.deserialize({
      element: '<p>This is <b>bold</b> and <i>italic</i> text!</p>',
    }),
});

For a comprehensive list of plugins that support HTML string deserialization, refer to the Plugin Deserialization Rules section.

Schema Identity

Plate compiles the document schema from the installed plugins. Omit schema for ordinary editors. editor.read.schema.identity() returns a derived identity with the exact compiled fingerprint:

const editor = createPlateEditor({
  plugins: [HeadingPlugin],
});
 
editor.read.schema.identity();
// { kind: 'derived', fingerprint: '...' }
const editor = createPlateEditor({
  plugins: [HeadingPlugin],
});
 
editor.read.schema.identity();
// { kind: 'derived', fingerprint: '...' }

Put id and version in schema when a document participates in durable persistence, history serialization, schema migration, or collaboration peer negotiation. The same object owns application schema overrides and named lineage. The fingerprint remains tied only to the compiled schema:

const editor = createPlateEditor({
  plugins: [HeadingPlugin],
  schema: { id: 'acme-document', version: 3 },
});
 
editor.read.schema.identity();
// { kind: 'named', id: 'acme-document', version: 3, fingerprint: '...' }
const editor = createPlateEditor({
  plugins: [HeadingPlugin],
  schema: { id: 'acme-document', version: 3 },
});
 
editor.read.schema.identity();
// { kind: 'named', id: 'acme-document', version: 3, fingerprint: '...' }

Bump version whenever the named lineage changes schema semantics. Reusing an ID and version with a different fingerprint is an identity mismatch.

Custom Primary Root

Omit schema.root for Plate's standard nonempty paragraph root. Declare it only when the application needs a different top-level grammar.

import { schema } from 'platejs';
import {
  createPlateEditor,
  definePlatePlugin,
  ParagraphPlugin,
} from 'platejs/react';
 
const SectionPlugin = definePlatePlugin('section', {
  schema: {
    element: {
      content: schema.content.element(ParagraphPlugin, { min: 1 }),
    },
  },
});
 
const editor = createPlateEditor({
  plugins: [SectionPlugin],
  schema: {
    root: schema.content.element(SectionPlugin, { min: 1 }),
  },
});
import { schema } from 'platejs';
import {
  createPlateEditor,
  definePlatePlugin,
  ParagraphPlugin,
} from 'platejs/react';
 
const SectionPlugin = definePlatePlugin('section', {
  schema: {
    element: {
      content: schema.content.element(ParagraphPlugin, { min: 1 }),
    },
  },
});
 
const editor = createPlateEditor({
  plugins: [SectionPlugin],
  schema: {
    root: schema.content.element(SectionPlugin, { min: 1 }),
  },
});

root.min is required and must be a positive integer. Root descriptors must match the installed plugin family: use Plate descriptors with createPlateEditor and Base descriptors with a headless Base plugin tuple. For schema.content.elements([...]), the first descriptor constructs the default root child.

A custom root changes the compiled fingerprint. For a named persisted schema, increment version and add the document migration before loading documents that use the earlier root grammar. Generated Value types include every legal root variant; min and max remain runtime validation rules.

Document Migrations

Persist schema identity beside the document, then declare one application-owned migration chain. Target-version steps run in ascending order before installed plugin preparation and schema fitting.

src/editor.ts
import { ParagraphPlugin } from 'platejs/react';
import {
  defineDocumentMigrations,
  migratePlateV54,
  migratePlateV55,
} from 'platejs/migrations';
 
import { fingerprint as v53Fingerprint } from './migrations/v54-upgrade-plate/from';
import { fingerprint as v54Fingerprint } from './migrations/v55-upgrade-plate/from';
 
export const EditorKit = [ParagraphPlugin] as const;
 
export const EditorSchema = {
  id: 'acme-document',
  version: 55,
} as const;
 
export const EditorMigrations = defineDocumentMigrations(EditorSchema, {
  sourceFingerprints: { 53: v53Fingerprint, 54: v54Fingerprint },
  steps: { 54: migratePlateV54, 55: migratePlateV55 },
  unversioned: 53,
});
src/editor.ts
import { ParagraphPlugin } from 'platejs/react';
import {
  defineDocumentMigrations,
  migratePlateV54,
  migratePlateV55,
} from 'platejs/migrations';
 
import { fingerprint as v53Fingerprint } from './migrations/v54-upgrade-plate/from';
import { fingerprint as v54Fingerprint } from './migrations/v55-upgrade-plate/from';
 
export const EditorKit = [ParagraphPlugin] as const;
 
export const EditorSchema = {
  id: 'acme-document',
  version: 55,
} as const;
 




Pass the same three exports to the editor. unversioned is an explicit support floor for stored documents without schema metadata; remove it when that data is no longer supported.

src/document-editor.tsx
import { Plate, usePlateEditor } from 'platejs/react';
 
import { EditorKit, EditorMigrations, EditorSchema } from './editor';
 
export function DocumentEditor({ persisted }) {
  const editor = usePlateEditor({
    initialValue: persisted,
    migrations: EditorMigrations,
    plugins: EditorKit,
    schema: EditorSchema,
  });
 
  return <Plate editor={editor} />;
}
src/document-editor.tsx
import { Plate, usePlateEditor } from 'platejs/react';
 
import { EditorKit, EditorMigrations, EditorSchema } from './editor';
 
export function DocumentEditor({ persisted }) {
  const editor = usePlateEditor({
    initialValue: persisted,
    migrations: EditorMigrations,
    plugins: EditorKit,
    schema: EditorSchema,
  });
 
  return <Plate editor={editor} />;
}

Store the complete envelope returned by your application boundary:

const persisted = {
  document: value,
  schema: editor.read.schema.identity(),
};
const persisted = {
  document: value,
  schema: editor.read.schema.identity(),
};

A document at version 53 loaded by a version 55 editor runs steps 54 and 55. Plate rejects a missing step, a different schema ID, a future version, or a fingerprint mismatch instead of fitting incompatible data. Every historical envelope version needs its exact generated fingerprint in sourceFingerprints; raw documents use the explicit unversioned floor.

History and collaboration need a cutover

Document migrations do not rewrite serialized history or a populated Yjs room. Invalidate or migrate stored history offline, and connect upgraded clients to a new schema-versioned room after migrating its snapshot. Do not mix peers from different schema versions.

Remote Initial Value

Fetch remote content before constructing the editor. The loader owns aborts, stale responses, retries, and errors; the editor receives one synchronous initial document.

import type { Value } from 'platejs';
import { Plate, usePlateEditor } from 'platejs/react';
 
function DocumentEditor({ initialValue }: { initialValue: Value }) {
  const editor = usePlateEditor({ initialValue });
 
  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor />
      </EditorContainer>
    </Plate>
  );
}
import type { Value } from 'platejs';
import { Plate, usePlateEditor } from 'platejs/react';
 
function DocumentEditor({ initialValue }: { initialValue: Value }) {
  const editor = usePlateEditor({ initialValue });
 
  return (
    <Plate editor={editor}>
      <EditorContainer>
        <Editor />
      </EditorContainer>
    </Plate>
  );
}

Render DocumentEditor only after your route or data loader resolves the document. See Controlled Editor Value for an abort-aware client loader and explicit replacement patterns.

Adding Plugins

You can add plugins to your editor by including them in the plugins array:

const editor = createPlateEditor({
  plugins: [HeadingPlugin, ListPlugin],
});
const editor = createPlateEditor({
  plugins: [HeadingPlugin, ListPlugin],
});

Exact Generated Editor Types

Raw plugin arrays keep plugin APIs, reads, updates, stores, and individual element shapes inferred. Use a generated editor contract when the application also needs one exact recursive Value across the complete plugin graph.

Export the runtime inputs from one source file:

src/editor.ts
import { HeadingPlugin } from '@platejs/basic-nodes/react';
import { TablePlugin } from '@platejs/table/react';
import { property, schema as s, target } from 'platejs';
 
export const EditorKit = [HeadingPlugin, TablePlugin] as const;
 
export const EditorSchema = {
  id: 'app-document',
  version: 1,
  overrides: [
    s.override(HeadingPlugin, {
      element: { type: 'headingOne' },
    }),
  ],
  properties: {
    reviewState: s.elementProperty(
      property.enum(['draft', 'approved'] as const),
      { target: target.element(HeadingPlugin) }
    ),
  },
} as const;
src/editor.ts
import { HeadingPlugin } from '@platejs/basic-nodes/react';
import { TablePlugin } from '@platejs/table/react';
import { property, schema as s, target } from 'platejs';
 
export const EditorKit = [HeadingPlugin, TablePlugin] as const;
 
export const EditorSchema = {
  id: 'app-document',
  version: 1,
  overrides: [
    s.override(HeadingPlugin, {
      element: { type: 'headingOne' },
    }),
  ],
  properties: {
    reviewState: s.elementProperty(
      property.enum



plate generate finds the single exported Plate plugin tuple and optional application schema by their validated runtime shapes. Their export names belong to your app, not the compiler contract.

The editor-level schema may remap an element type, content, groups, or an existing property's target, and it may add application-owned properties. Plugin-owned property keys and value laws stay fixed; changing one requires a new field and an explicit migration.

Installed code reads final identities from schema handles:

editor.plugin(HeadingPlugin).schema.type; // 'headingOne'
generatedSchema.properties.reviewState.key; // 'reviewState'
editor.plugin(HeadingPlugin).schema.type; // 'headingOne'
generatedSchema.properties.reviewState.key; // 'reviewState'

Generate and commit the TypeScript and JSON contracts:

pnpm add -D @platejs/cli
pnpm exec plate generate
pnpm add -D @platejs/cli
pnpm exec plate generate

plate generate reads src/editor.ts. Pass entry paths when an app owns several editor modules. The @plate/editor-plugins registry item installs the authored plugin module only; it does not copy generated contracts.

Add --watch during development. Gate committed artifacts in CI:

package.json
{
  "scripts": {
    "editor:check": "plate generate --check src/editor.ts"
  }
}
package.json
{
  "scripts": {
    "editor:check": "plate generate --check src/editor.ts"
  }
}

The generated module exports exact editor/value types, static schema handles, and a fingerprint. It does not own runtime plugin composition:

src/editor/document-editor.tsx
import { Plate, usePlateEditor } from 'platejs/react';
 
import {
  schema as generatedSchema,
  type Editor,
  type Value,
} from './plugins.generated';
import { EditorKit, EditorSchema } from './editor';
 
export function insertDocumentContent(editor: Editor) {
  editor.update.heading.insert({ level: 1 });
  editor.update.table.set({ marginLeft: 24 });
}
 
export function DocumentEditor({ initialValue }: { initialValue: Value }) {
  const editor = usePlateEditor({
    plugins: EditorKit,
    schema: EditorSchema,
    initialValue,
  });
 
  generatedSchema.properties.reviewState.key;
 
  return <Plate editor={editor} />;
}
 
export type DocumentEditorInstance = Editor;
src/editor/document-editor.tsx
import { Plate, usePlateEditor } from 'platejs/react';
 
import {
  schema as generatedSchema,
  type Editor,
  type Value,
} from './plugins.generated';
import { EditorKit, EditorSchema } from './editor';
 
export function insertDocumentContent(editor: Editor) {
  editor.update.heading.insert({ level: 1 });
  editor.update.table.set({ marginLeft: 24 });
}
 
export function DocumentEditor({ initialValue }: { initialValue: Value }) {











Use generated Value and Editor only at static boundaries such as storage, collaboration, and exported application types. Runtime constructors and hooks consume the authored EditorKit and EditorSchema directly.

The CI command recompiles the authored module and fails when committed generated artifacts are stale.

Create typed application steps with the migration scaffold:

pnpm exec plate migrate new add-caption
pnpm exec plate migrate new add-caption

Pass --entry <path> when the editor module does not use the standard path.

The scaffold contains typed FromValue and ToValue snapshots, their fingerprints, and the structural diff. Add the completed function to EditorMigrations.steps under the version it produces, and bind the exported from fingerprint in sourceFingerprints.

The editor runs configured migrations for initial and deferred complete document loads. For offline JSON files, the entry module exports the exact EditorKit, EditorSchema, and EditorMigrations names used below. Explicit names keep the executable runner deterministic even when the module exports other arrays or schema-like objects.

pnpm exec plate migrate run --entry src/editor.ts --check documents/*.json
pnpm exec plate migrate run --entry src/editor.ts --write documents/*.json
cat document.json | pnpm exec plate migrate run --entry src/editor.ts --stdin
pnpm exec plate migrate run --entry src/editor.ts --check documents/*.json
pnpm exec plate migrate run --entry src/editor.ts --write documents/*.json
cat document.json | pnpm exec plate migrate run --entry src/editor.ts --stdin

The command is a dry run unless --write is present. --check exits nonzero when any document needs migration. --stdin writes the migrated envelope to standard output and never mutates storage. Envelope selections are mapped and preserved through the same document and preparation pipeline.

Max Length

Set the maximum length of the editor:

const editor = createPlateEditor({
  maxLength: 100,
});
const editor = createPlateEditor({
  maxLength: 100,
});

Advanced Configuration

Editor ID

Set a custom id for the editor:

const editor = createPlateEditor({
  id: 'my-custom-editor-id',
});
const editor = createPlateEditor({
  id: 'my-custom-editor-id',
});

Pass the same ID to controller-aware hooks when selecting a specific mounted editor, for example useEditor({ id: 'my-custom-editor-id' }).

Node Identity

Every live descendant has an editor-scoped NodeKey. Node keys include text nodes, survive moves and immutable updates, and disappear when the node is removed. They never enter JSON, clipboard slices, history, or collaboration payloads. Use them for selection, drag and drop, temporary UI state, and lazy path lookup.

const nodeKey = editor.key(element);
const path = editor.read.nodes.path(nodeKey);
 
editor.update.nodes.remove({ at: nodeKey });
const nodeKey = editor.key(element);
const path = editor.read.nodes.path(nodeKey);
 
editor.update.nodes.remove({ at: nodeKey });

Node keys can target nodes across one editor's document roots. Path lookup is root-local: the base editor resolves paths in the main root, while an editor view resolves paths in its own root.

Use ElementIdPlugin only when an element needs an ID that survives storage or cross-session references. The plugin is opt-in. It assigns persisted string IDs to block and inline elements, never text nodes.

import { ElementIdPlugin } from "platejs";
 
const editor = usePlateEditor({
  plugins: [
    ElementIdPlugin.configure({
      initialState: {
        generateId: () => crypto.randomUUID(),
      },
    }),
  ],
});
 
const elementId = editor.plugin(ElementIdPlugin);
const key = editor.key(element);
const id = elementId.read.id(key);
const entry = id ? elementId.read.entry(id) : undefined;
import { ElementIdPlugin } from "platejs";
 
const editor = usePlateEditor({
  plugins: [
    ElementIdPlugin.configure({
      initialState: {
        generateId: () => crypto.randomUUID(),
      },
    }),
  ],
});
 
const elementId = editor.plugin(ElementIdPlugin);
const key = editor.key(element);
const id = elementId.read.id(key);
const entry = id ? elementId.read.entry(id) : undefined;

The default generator is full-length nanoid(). Loads and moves preserve valid IDs. Copies, splits, duplicates, and pasted copies receive fresh IDs. Explicit duplicates are rejected. Exact schema-derived elements expose element.id; use the plugin read at erased or optionally installed package boundaries.

The compiled schema property target decides which elements receive IDs. Narrow the target in the application schema when only blocks need persisted identity:

import { ElementIdPlugin, schema, target } from "platejs";
 
const editor = usePlateEditor({
  plugins: [ElementIdPlugin],
  schema: {
    overrides: [
      schema.override(ElementIdPlugin, {
        properties: { id: { target: target.group("block") } },
      }),
    ],
  },
});
import { ElementIdPlugin, schema, target } from "platejs";
 
const editor = usePlateEditor({
  plugins: [ElementIdPlugin],
  schema: {
    overrides: [
      schema.override(ElementIdPlugin, {
        properties: { id: { target: target.group("block") } },
      }),
    ],
  },
});

Document preparation generates IDs only for matching elements and removes the plugin-owned id from excluded elements.

Use the pure migration helper before loading stored documents that lack IDs. It preserves valid strings, fills missing IDs, reports duplicates, and requires an explicit policy for legacy numeric IDs.

import { migrateElementIds, nanoid } from "platejs";
 
const result = migrateElementIds(storedValue, {
  convertNumericId: (id) => `legacy-${id}`,
  generateId: nanoid,
  sourceKey: 'legacyElementId',
});
 
if (result.duplicates.length > 0) {
  throw new Error("Resolve duplicate element IDs before loading the document.");
}
 
const editor = createPlateEditor({
  plugins: [ElementIdPlugin],
  initialValue: result.value,
});
import { migrateElementIds, nanoid } from "platejs";
 
const result = migrateElementIds(storedValue, {
  convertNumericId: (id) => `legacy-${id}`,
  generateId: nanoid,
  sourceKey: 'legacyElementId',
});
 
if (result.duplicates.length > 0) {
  throw new Error("Resolve duplicate element IDs before loading the document.");
}
 
const editor = createPlateEditor({
  plugins: [ElementIdPlugin],
  initialValue: result.value,
});

Navigation Feedback

Plate also includes a built-in navigation feedback plugin for "you landed here" UX after TOC jumps, footnote navigation, search jumps, and custom outline movement.

This feature is enabled by default. You only need to touch the navigationFeedback option when you want to change the flash duration or turn the plugin off.

Configuration

const editor = createPlateEditor({
  navigationFeedback: {
    duration: 1200,
  },
});
const editor = createPlateEditor({
  navigationFeedback: {
    duration: 1200,
  },
});

The NavigationFeedbackPlugin is part of the React editor defaults. Use navigationFeedback for its editor-level configuration.

Disabling Navigation Feedback

const editor = createPlateEditor({
  navigationFeedback: false,
});
const editor = createPlateEditor({
  navigationFeedback: false,
});

Normalization

Control whether the editor should normalize its content on initialization:

const editor = createPlateEditor({
  shouldNormalizeEditor: true,
});
const editor = createPlateEditor({
  shouldNormalizeEditor: true,
});

Note that normalization may take a few dozen milliseconds for large documents, such as the playground value.

Auto-selection

Configure the editor to automatically select a range:

const editor = createPlateEditor({
  autoSelect: 'end', // or 'start', or true
});
const editor = createPlateEditor({
  autoSelect: 'end', // or 'start', or true
});

This is not the same as auto-focus: you can select text without focusing the editor.

Component Overrides

Override default components for plugins:

const editor = createPlateEditor({
  plugins: [HeadingPlugin],
  components: {
    paragraph: CustomParagraphComponent,
    heading: CustomHeadingComponent,
  },
});
const editor = createPlateEditor({
  plugins: [HeadingPlugin],
  components: {
    paragraph: CustomParagraphComponent,
    heading: CustomHeadingComponent,
  },
});

Configure Plugins

When your app imports the target, configure that descriptor directly:

const AppLinkPlugin = LinkPlugin.configure({
  initialState: {
    allowedSchemes: ['http', 'https'],
  },
});
 
const editor = createPlateEditor({
  plugins: [AppLinkPlugin],
});
const AppLinkPlugin = LinkPlugin.configure({
  initialState: {
    allowedSchemes: ['http', 'https'],
  },
});
 
const editor = createPlateEditor({
  plugins: [AppLinkPlugin],
});

Weak Peer Overrides

A package plugin may need to adapt another installed package without importing it or controlling the consumer's editor kit:

import { PLUGINS } from 'platejs';
import { definePlatePlugin } from 'platejs/react';
 
const SingleBlockPlugin = definePlatePlugin(PLUGINS.singleBlock, {
  override: {
    plugins: {
      [PLUGINS.trailingBlock]: {
        enabled: false,
      },
    },
  },
});
import { PLUGINS } from 'platejs';
import { definePlatePlugin } from 'platejs/react';
 
const SingleBlockPlugin = definePlatePlugin(PLUGINS.singleBlock, {
  override: {
    plugins: {
      [PLUGINS.trailingBlock]: {
        enabled: false,
      },
    },
  },
});

The override applies only when the target is installed; a missing target is ignored. It cannot change name, dependencies, or nest another override. Direct target configuration wins.

Replace Dependency Defaults

A complete explicit descriptor can replace a lower-precedence core or dependency definition with the same name. Inside one plugin array, terminal configurations derived from the same authored plugin compose in order and later defined values win. Unrelated plugins and divergent authoring branches cannot share a name.

const AppParagraphPlugin = ParagraphPlugin.configure({
  component: AppParagraphElement,
});
 
const editor = createPlateEditor({
  plugins: [AppParagraphPlugin],
});
const AppParagraphPlugin = ParagraphPlugin.configure({
  component: AppParagraphElement,
});
 
const editor = createPlateEditor({
  plugins: [AppParagraphPlugin],
});

Typed Editor

createPlateEditor derives the document value and plugin APIs from the installed plugin tuple. initialValue is checked against that schema-derived value.

Plugins Type

const AppKit = [TablePlugin, LinkPlugin] as const;
const editor = createPlateEditor({
  plugins: AppKit,
});
 
// Usage
editor.update((tx) => {
  tx.table.insertRow();
});
const AppKit = [TablePlugin, LinkPlugin] as const;
const editor = createPlateEditor({
  plugins: AppKit,
});
 
// Usage
editor.update((tx) => {
  tx.table.insertRow();
});

Value Type

Use PlateEditor<typeof Kit> when another module needs the editor type and ValueOf<MyEditor> when a named value type improves readability. Do not repeat the schema as handwritten element interfaces.

import type { ValueOf } from 'platejs';
import type { PlateEditor } from 'platejs/react';
 
const AppKit = [TablePlugin, LinkPlugin] as const;
 
export type MyEditor = PlateEditor<typeof AppKit>;
export type MyValue = ValueOf<MyEditor>;
 
const value: MyValue = [{
  type: 'paragraph',
  children: [{ text: 'Hello, Plate!' }],
}];
 
const editor = createPlateEditor({
  plugins: AppKit,
  initialValue: value,
});
import type { ValueOf } from 'platejs';
import type { PlateEditor } from 'platejs/react';
 
const AppKit = [TablePlugin, LinkPlugin] as const;
 
export type MyEditor = PlateEditor<typeof AppKit>;
export type MyValue = ValueOf<MyEditor>;
 
const value: MyValue = [{
  type: 'paragraph',
  children: [{ text: 'Hello, Plate!' }],
}];
 
const editor = createPlateEditor({
  plugins: AppKit,

A component reads the current editor from context without repeating its plugin tuple:

const editor = useEditor();
const table = useEditorPlugin(TablePlugin);
const editor = useEditor();
const table = useEditorPlugin(TablePlugin);

Use useEditorPlugin(Plugin) when the component needs an exact plugin capability. Generated application types stay at explicit static boundaries.

export const EditorMigrations = defineDocumentMigrations(EditorSchema, {
sourceFingerprints: { 53: v53Fingerprint, 54: v54Fingerprint },
steps: { 54: migratePlateV54, 55: migratePlateV55 },
unversioned: 53,
});
([
'draft'
,
'approved'
]
as
const
),
{ target: target.element(HeadingPlugin) }
),
},
} as const;
const editor = usePlateEditor({
plugins: EditorKit,
schema: EditorSchema,
initialValue,
});
generatedSchema.properties.reviewState.key;
return <Plate editor={editor} />;
}
export type DocumentEditorInstance = Editor;
initialValue: value,
});