Plate Plugin Creator
Repo-specific companion to Plate's core plugin APIs.
Before shaping a reusable public API, architecture decision, builder/factory
pattern, naming convention, runtime/service boundary, or perf-sensitive public
surface, read north-star first. This skill is the
execution companion, not the constitutional source of truth.
Use this skill for Plate-specific plugin authorship: semantic ownership,
authoring order, type-contract fidelity, and React/Plate wrapper boundaries.
Use docs-creator for public plugin docs.
North-Star Gate
Binary contract:
- If the lane materially changes reusable API shape, runtime boundaries,
builder/factory patterns, or reusable naming/layering, stop and route to
north-star first. Then include:
north-star updated
- or
north-star reaffirmed: <section-name>
- If not, continue here for plugin execution mechanics.
Owner map:
| Owner |
Scope |
north-star |
doctrine, API shape, runtime boundaries, perf law |
plate-plugin-creator |
plugin mechanics, typing, wrappers, file placement |
Do not restate long-form north-star law, precedence, or anti-pattern prose
here. Keep only the routing gate, short derived checklist, and execution
mechanics.
Derived checklist from north-star:
- Is the owner/layer explicit?
- Is this canonical semantics or local sugar?
- Is the config explicit and copyable?
- Does the shape add hidden runtime work on the hot path?
- Does core own a primitive here instead of feature semantics?
Repo Surfaces
packages/*/src/lib — semantic base plugins, transforms, parsers, rules
packages/*/src/react — Plate/React wrappers, hooks, node props, components
packages/core/src/lib/plugin — Slate-first authoring primitives
packages/core/src/react/plugin — Plate wrapper primitives
packages/core/type-tests — plugin contract source of truth
Principles
- Start where semantics live. If the behavior matters without React, start
in
src/lib.
- Use inference before ceremony. Reach for
createT* only when explicit
contract control buys something real.
- Wrap base plugins. If a semantic base already exists, lift it with
toPlatePlugin or toTPlatePlugin instead of re-authoring it in React.
- Design the API shape on purpose. Plugin-specific surfaces and merged
editor surfaces are different tools.
- Use shared keys. Shipped plugin keys and cross-plugin references should
come from
packages/utils/src/lib/plate-keys.ts, not random string literals.
- Core contracts beat precedent. Type tests and core authoring APIs outrank
noisy old package examples.
- Keep the lane narrow. This skill owns plugin authoring, not public docs.
Critical Rules
Barrel & File Placement
- Never hand-write or hand-edit
index.ts / index.tsx barrel files. Treat
them as generated output only.
- When adding, moving, renaming, or deleting public files under exported
package folders, run
pnpm brl after the file work and before final
verification.
- If
pnpm brl produces a broken barrel, fix the barrel generator/config or
file placement. Do not patch the generated index.ts by hand after brl.
- Any helper, matcher, fallback branch, or possible future fork logic that is
not part of the intended public contract should live under an
internal/
directory.
- Default to
internal/ unless the user-facing API genuinely needs the file to
be importable.
- Start with the decision tree before writing code.
createSlatePlugin / createTSlatePlugin own semantic base plugins.
toPlatePlugin / toTPlatePlugin lift a semantic base into the React/Plate
surface.
createPlatePlugin / createTPlatePlugin are for real React/Plate-native
plugins or bundles of existing Plate plugins.
- If you only need to bundle existing Plate plugins, do not invent a fake base
plugin first.
Typing & Context → typing.md
- Callback context already provides
editor, plugin, type, api, tf,
getOptions, setOption, and friends. Use them.
- Forbid
any in source files. The only acceptable exception is non-type test
code where the looseness is intentional and local to the test.
- Do not thread
SlateEditor through callbacks, options, or helper signatures
when plugin context already has the editor.
- Prefer
KEYS from packages/utils/src/lib/plate-keys.ts for shipped/shared
plugin keys and cross-plugin references. Use editor.getType(KEYS.foo) when
you need the resolved node type.
createTSlatePlugin and createTPlatePlugin are explicit-contract tools, not
default ceremony.
- Trust
packages/core/type-tests/* over stale package precedent.
extendApi / extendTransforms are plugin-specific surfaces.
extendEditorApi / extendEditorTransforms feed the merged editor surface.
- Use
configurePlugin to override nested child plugins instead of cloning
their config by hand.
- Use
overrideEditor when the real ownership is editor behavior, not random
event glue.
- For React-only augmentation of existing rendered nodes, prefer
inject.nodeProps.transformProps before inventing wrapper components or
heavier node plumbing. This is especially right when the augmentation needs
hooks.
Hard Law
Slate-first, Plate-second.
If a plugin has meaningful document semantics without React, author the base in
packages/*/src/lib first. Add the React/Plate layer only when rendering,
hooks, or Plate-only editor integration is actually needed.
Named exceptions:
- React-only hook or
useHooks plugins
- DOM/editor-surface plugins with no meaningful Slate-only base
- Plate-only bundle plugins that just compose existing Plate plugins
- React node-prop injection that truly depends on hooks or component context
Do Not Copy
- Do not start in
src/react just because the consumer eventually uses React.
- Do not re-author a base plugin with
createPlatePlugin just to add a
component or small wrapper config.
- Do not hardcode shipped/shared plugin keys when
KEYS already owns that
contract.
- Do not cargo-cult
({ editor }: { editor: SlateEditor }) => ... callback
annotations when inference already knows the editor type.
- Do not extract editor-locked helpers just to placate TypeScript.
- Do not create new public top-level files when
internal/ is enough.
- Do not treat
transformProps like a universal replacement for
node.component, render, or useHooks. Use it when the real job is prop
augmentation.
- Do not trust the loudest old plugin file over core APIs and type tests.
Key Patterns
// Good: semantic base first, thin Plate wrapper second.
export const BaseCommentPlugin = createTSlatePlugin<BaseCommentConfig>({
key: KEYS.comment,
}).extendApi(...);
export const CommentPlugin = toPlatePlugin(BaseCommentPlugin);
// Good: wrapper adds Plate-only child wiring without re-authoring semantics.
export const CodeBlockPlugin = toPlatePlugin(BaseCodeBlockPlugin, {
plugins: [CodeLinePlugin, CodeSyntaxPlugin],
});
// Good: direct Plate plugin when the job is React/editor integration.
export const EventEditorPlugin = createPlatePlugin({
key: 'eventEditor',
handlers: { ... },
});
// Good: bundle existing Plate plugins without fake base-plugin theater.
export const BasicBlocksPlugin = createPlatePlugin({
plugins: [BlockquotePlugin, HeadingPlugin, HorizontalRulePlugin],
});
Workflow
- Read creation-flow.md before choosing an API.
- Search the closest analog in
packages/*/src/lib, packages/*/src/react,
and packages/core/type-tests.
- Decide whether this is:
- a semantic base plugin
- a Plate/React wrapper
- a React-native exception
- a bundle plugin
- Lock the contract shape:
- options
- plugin-specific API/transforms
- merged editor API/transforms
- nested child plugins
- Apply the typing rules before adding explicit annotations.
- Add docs only by handing off to docs-creator.
- Verify the smallest honest surface:
- package tests for semantic/plugin behavior
- type tests or targeted typecheck when public contract changed
- React tests only when the wrapper layer changed
Audit References
Detailed References
1---2name: plate-plugin-creator3description: Build new Plate plugins with Slate-first architecture, sane typing, and explicit React/Plate wrapper boundaries. Use when authoring or refactoring Plate plugin packages, deciding between createSlatePlugin vs createPlatePlugin, defining plugin APIs/transforms/options, or lifting semantic base plugins into React/Plate wrappers.4---56# Plate Plugin Creator78Repo-specific companion to Plate's core plugin APIs.910Before shaping a reusable public API, architecture decision, builder/factory11pattern, naming convention, runtime/service boundary, or perf-sensitive public12surface, read [north-star](../north-star/SKILL.md) first. This skill is the13execution companion, not the constitutional source of truth.1415Use this skill for Plate-specific plugin authorship: semantic ownership,16authoring order, type-contract fidelity, and React/Plate wrapper boundaries.1718Use [docs-creator](../docs-creator/SKILL.md) for public plugin docs.1920## North-Star Gate2122Binary contract:2324- If the lane materially changes reusable API shape, runtime boundaries,25 builder/factory patterns, or reusable naming/layering, stop and route to26 [north-star](../north-star/SKILL.md) first. Then include:27 - `north-star updated`28 - or `north-star reaffirmed: <section-name>`29- If not, continue here for plugin execution mechanics.3031Owner map:3233| Owner | Scope |34| --- | --- |35| `north-star` | doctrine, API shape, runtime boundaries, perf law |36| `plate-plugin-creator` | plugin mechanics, typing, wrappers, file placement |3738Do not restate long-form `north-star` law, precedence, or anti-pattern prose39here. Keep only the routing gate, short derived checklist, and execution40mechanics.4142Derived checklist from `north-star`:43441. Is the owner/layer explicit?452. Is this canonical semantics or local sugar?463. Is the config explicit and copyable?474. Does the shape add hidden runtime work on the hot path?485. Does core own a primitive here instead of feature semantics?4950## Repo Surfaces5152- `packages/*/src/lib` — semantic base plugins, transforms, parsers, rules53- `packages/*/src/react` — Plate/React wrappers, hooks, node props, components54- `packages/core/src/lib/plugin` — Slate-first authoring primitives55- `packages/core/src/react/plugin` — Plate wrapper primitives56- `packages/core/type-tests` — plugin contract source of truth5758## Principles59601. **Start where semantics live.** If the behavior matters without React, start61 in `src/lib`.622. **Use inference before ceremony.** Reach for `createT*` only when explicit63 contract control buys something real.643. **Wrap base plugins.** If a semantic base already exists, lift it with65 `toPlatePlugin` or `toTPlatePlugin` instead of re-authoring it in React.664. **Design the API shape on purpose.** Plugin-specific surfaces and merged67 editor surfaces are different tools.685. **Use shared keys.** Shipped plugin keys and cross-plugin references should69 come from `packages/utils/src/lib/plate-keys.ts`, not random string literals.706. **Core contracts beat precedent.** Type tests and core authoring APIs outrank71 noisy old package examples.727. **Keep the lane narrow.** This skill owns plugin authoring, not public docs.7374## Critical Rules7576### Barrel & File Placement7778- Never hand-write or hand-edit `index.ts` / `index.tsx` barrel files. Treat79 them as generated output only.80- When adding, moving, renaming, or deleting public files under exported81 package folders, run `pnpm brl` after the file work and before final82 verification.83- If `pnpm brl` produces a broken barrel, fix the barrel generator/config or84 file placement. Do not patch the generated `index.ts` by hand after `brl`.85- Any helper, matcher, fallback branch, or possible future fork logic that is86 not part of the intended public contract should live under an `internal/`87 directory.88- Default to `internal/` unless the user-facing API genuinely needs the file to89 be importable.9091### Creation Flow → [creation-flow.md](./rules/creation-flow.md)9293- Start with the decision tree before writing code.94- `createSlatePlugin` / `createTSlatePlugin` own semantic base plugins.95- `toPlatePlugin` / `toTPlatePlugin` lift a semantic base into the React/Plate96 surface.97- `createPlatePlugin` / `createTPlatePlugin` are for real React/Plate-native98 plugins or bundles of existing Plate plugins.99- If you only need to bundle existing Plate plugins, do not invent a fake base100 plugin first.101102### Typing & Context → [typing.md](./rules/typing.md)103104- Callback context already provides `editor`, `plugin`, `type`, `api`, `tf`,105 `getOptions`, `setOption`, and friends. Use them.106- Forbid `any` in source files. The only acceptable exception is non-type test107 code where the looseness is intentional and local to the test.108- Do not thread `SlateEditor` through callbacks, options, or helper signatures109 when plugin context already has the editor.110- Prefer `KEYS` from `packages/utils/src/lib/plate-keys.ts` for shipped/shared111 plugin keys and cross-plugin references. Use `editor.getType(KEYS.foo)` when112 you need the resolved node type.113- `createTSlatePlugin` and `createTPlatePlugin` are explicit-contract tools, not114 default ceremony.115- Trust `packages/core/type-tests/*` over stale package precedent.116117### Composition & API Shape → [composition.md](./rules/composition.md)118119- `extendApi` / `extendTransforms` are plugin-specific surfaces.120- `extendEditorApi` / `extendEditorTransforms` feed the merged editor surface.121- Use `configurePlugin` to override nested child plugins instead of cloning122 their config by hand.123- Use `overrideEditor` when the real ownership is editor behavior, not random124 event glue.125- For React-only augmentation of existing rendered nodes, prefer126 `inject.nodeProps.transformProps` before inventing wrapper components or127 heavier node plumbing. This is especially right when the augmentation needs128 hooks.129130## Hard Law131132**Slate-first, Plate-second.**133134If a plugin has meaningful document semantics without React, author the base in135`packages/*/src/lib` first. Add the React/Plate layer only when rendering,136hooks, or Plate-only editor integration is actually needed.137138Named exceptions:1391401. React-only hook or `useHooks` plugins1412. DOM/editor-surface plugins with no meaningful Slate-only base1423. Plate-only bundle plugins that just compose existing Plate plugins1434. React node-prop injection that truly depends on hooks or component context144145## Do Not Copy146147- Do not start in `src/react` just because the consumer eventually uses React.148- Do not re-author a base plugin with `createPlatePlugin` just to add a149 component or small wrapper config.150- Do not hardcode shipped/shared plugin keys when `KEYS` already owns that151 contract.152- Do not cargo-cult `({ editor }: { editor: SlateEditor }) => ...` callback153 annotations when inference already knows the editor type.154- Do not extract editor-locked helpers just to placate TypeScript.155- Do not create new public top-level files when `internal/` is enough.156- Do not treat `transformProps` like a universal replacement for157 `node.component`, `render`, or `useHooks`. Use it when the real job is prop158 augmentation.159- Do not trust the loudest old plugin file over core APIs and type tests.160161## Key Patterns162163```ts164// Good: semantic base first, thin Plate wrapper second.165export const BaseCommentPlugin = createTSlatePlugin<BaseCommentConfig>({166 key: KEYS.comment,167}).extendApi(...);168169export const CommentPlugin = toPlatePlugin(BaseCommentPlugin);170171// Good: wrapper adds Plate-only child wiring without re-authoring semantics.172export const CodeBlockPlugin = toPlatePlugin(BaseCodeBlockPlugin, {173 plugins: [CodeLinePlugin, CodeSyntaxPlugin],174});175176// Good: direct Plate plugin when the job is React/editor integration.177export const EventEditorPlugin = createPlatePlugin({178 key: 'eventEditor',179 handlers: { ... },180});181182// Good: bundle existing Plate plugins without fake base-plugin theater.183export const BasicBlocksPlugin = createPlatePlugin({184 plugins: [BlockquotePlugin, HeadingPlugin, HorizontalRulePlugin],185});186```187188## Workflow1891901. Read [creation-flow.md](./rules/creation-flow.md) before choosing an API.1912. Search the closest analog in `packages/*/src/lib`, `packages/*/src/react`,192 and `packages/core/type-tests`.1933. Decide whether this is:194 - a semantic base plugin195 - a Plate/React wrapper196 - a React-native exception197 - a bundle plugin1984. Lock the contract shape:199 - options200 - plugin-specific API/transforms201 - merged editor API/transforms202 - nested child plugins2035. Apply the typing rules before adding explicit annotations.2046. Add docs only by handing off to [docs-creator](../docs-creator/SKILL.md).2057. Verify the smallest honest surface:206 - package tests for semantic/plugin behavior207 - type tests or targeted typecheck when public contract changed208 - React tests only when the wrapper layer changed209210## Audit References211212- [plugin-authoring-audit.md](./references/plugin-authoring-audit.md) — real213 repo examples of good patterns and cautionary ones214215## Detailed References216217- [creation-flow.md](./rules/creation-flow.md)218- [typing.md](./rules/typing.md)219- [composition.md](./rules/composition.md)