From 6f9788f0035c0767f41bc945a158f54357a3ed2b Mon Sep 17 00:00:00 2001 From: Ziyi Yuan Date: Tue, 28 Apr 2026 12:31:28 +0200 Subject: [PATCH] docs(content-manager): add addRichTextBlocks API documentation --- .../wysiwyg-editor.md | 4 + .../content-manager-apis.md | 139 +++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/docusaurus/docs/cms/admin-panel-customization/wysiwyg-editor.md b/docusaurus/docs/cms/admin-panel-customization/wysiwyg-editor.md index b9fe3a77e6..23753b7fd9 100644 --- a/docusaurus/docs/cms/admin-panel-customization/wysiwyg-editor.md +++ b/docusaurus/docs/cms/admin-panel-customization/wysiwyg-editor.md @@ -11,6 +11,10 @@ tags: # Change the default rich text editor +:::note +This page covers customization of the **WYSIWYG markdown editor** used for `richtext` fields. For the **Blocks** field (the JSON-based rich text editor), see [Content Manager APIs: addRichTextBlocks](/cms/plugins-development/content-manager-apis#addrichtextblocks). +::: + Strapi's [admin panel](/cms/admin-panel-customization) comes with a built-in rich text editor. To change the default editor, several options are at your disposal: - You can install a third-party plugin, such as one for CKEditor, by visiting . diff --git a/docusaurus/docs/cms/plugins-development/content-manager-apis.md b/docusaurus/docs/cms/plugins-development/content-manager-apis.md index ef718e64fc..e5e656eee6 100644 --- a/docusaurus/docs/cms/plugins-development/content-manager-apis.md +++ b/docusaurus/docs/cms/plugins-development/content-manager-apis.md @@ -18,7 +18,7 @@ import InjectionVsCmApis from '/docs/snippets/injection-zones-vs-content-manager # Content Manager APIs -Content Manager APIs add panels and actions to list or edit views through `addEditViewSidePanel`, `addDocumentAction`, `addDocumentHeaderAction`, or `addBulkAction`. Each API accepts component functions with typed contexts, enabling precise control over document-aware UI injections. +Content Manager APIs add panels, actions, and custom rich text blocks to the Content Manager through `addEditViewSidePanel`, `addDocumentAction`, `addDocumentHeaderAction`, `addBulkAction`, or `addRichTextBlocks`. Each API accepts component functions with typed contexts, enabling precise control over document-aware UI injections. Content Manager APIs are part of the [Admin Panel API](/cms/plugins-development/admin-panel-api). They are a way for Strapi plugins to add content or options to the [Content Manager](/cms/features/content-manager). The Content Manager APIs allow you to extend the Content Manager by adding functionality from your own plugin, just like you can do it with [Injection zones](/cms/plugins-development/admin-injection-zones). @@ -380,4 +380,139 @@ interface BulkActionDescription { */ variant?: ButtonProps['variant']; } -``` \ No newline at end of file +``` + +### `addRichTextBlocks` + +Use this API to register custom block types in the [Blocks](/cms/features/content-manager) rich text field. Custom blocks appear in the toolbar dropdown alongside built-in ones. + +:::note +`addRichTextBlocks` must be called in the `register()` lifecycle function, not `bootstrap()`. The editor initializes its Slate instance during `register`, so blocks must be available at that point. +::: + +```jsx +addRichTextBlocks(blocks: RichTextBlocksStore | ((currentBlocks: RichTextBlocksStore) => RichTextBlocksStore)) +``` + +The API accepts 2 call signatures: + +- Passing an **object**: the provided blocks are merged into the existing blocks store. + + + + + ```js title="src/admin/app.js" + export default { + register(app) { + app.getPlugin('content-manager').apis.addRichTextBlocks({ + callout: { + renderElement: (props) => {props.children}, + icon: Information, + label: { id: 'my-plugin.blocks.callout', defaultMessage: 'Callout' }, + matchNode: (node) => node.type === 'callout', + isInBlocksSelector: true, + handleConvert(editor) { /* use Slate Transforms to set node type */ }, + snippets: [':::callout'], + }, + }); + }, + }; + ``` + + + + + ```tsx title="src/admin/app.ts" + import type { ContentManagerPlugin } from '@strapi/content-manager/strapi-admin'; + + export default { + register(app) { + const apis = + app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis']; + + apis.addRichTextBlocks({ + callout: { + renderElement: (props) => {props.children}, + icon: Information, + label: { id: 'my-plugin.blocks.callout', defaultMessage: 'Callout' }, + matchNode: (node) => node.type === 'callout', + isInBlocksSelector: true, + handleConvert(editor) { /* use Slate Transforms to set node type */ }, + snippets: [':::callout'], + }, + }); + }, + }; + ``` + + + + +- Passing a **function**: the function receives the current blocks store and must return the updated store. Use this form to remove or replace built-in blocks. + + + + + ```js title="src/admin/app.js" + export default { + register(app) { + app.getPlugin('content-manager').apis.addRichTextBlocks((currentBlocks) => { + // Remove the built-in code block + const { code: _removed, ...rest } = currentBlocks; + return rest; + }); + }, + }; + ``` + + + + + ```tsx title="src/admin/app.ts" + import type { + ContentManagerPlugin, + RichTextBlocksStore, + } from '@strapi/content-manager/strapi-admin'; + + export default { + register(app) { + const apis = + app.getPlugin('content-manager').apis as ContentManagerPlugin['config']['apis']; + + apis.addRichTextBlocks((currentBlocks: RichTextBlocksStore) => { + const { code: _removed, ...rest } = currentBlocks; + return rest; + }); + }, + }; + ``` + + + + +#### Block definition + +Each entry in the blocks object is a block definition with the following properties: + +| Property | Required | Description | +|---|---|---| +| `renderElement` | Yes | React render function. Spread `props.attributes` on the root element and render `props.children`. | +| `matchNode` | Yes | Returns `true` if a given Slate node belongs to this block type. | +| `isInBlocksSelector` | No | Set to `true` to show the block in the toolbar dropdown. Defaults to `false`. | +| `icon` | No | Icon component shown in the toolbar dropdown. Required when `isInBlocksSelector` is `true`. | +| `label` | No | `MessageDescriptor` (`{ id, defaultMessage }`) shown in the toolbar dropdown. Required when `isInBlocksSelector` is `true`. | +| `handleConvert` | No | Called when the user selects this block from the dropdown. Use Slate's `Transforms` to set the node type. | +| `handleEnterKey` | No | Custom Enter key behavior inside this block. | +| `handleBackspaceKey` | No | Custom Backspace key behavior. | +| `handleTab` | No | Custom Tab key behavior (e.g., indentation). | +| `handleShiftTab` | No | Custom Shift+Tab key behavior. | +| `snippets` | No | Typing one of these strings followed by Space triggers a conversion to this block type. | +| `dragHandleTopMargin` | No | Adjusts the vertical position of the drag-to-reorder grip icon. | +| `plugin` | No | A [Slate plugin](https://docs.slatejs.org/) registered when the editor instance is created. Use this for custom normalizers or Slate-level behavior. | +| `isDraggable` | No | Function returning whether a given element is draggable. Defaults to `() => true`. | + +The built-in block keys are: `paragraph`, `heading-one`, `heading-two`, `heading-three`, `heading-four`, `heading-five`, `heading-six`, `list-ordered`, `list-unordered`, `image`, `quote`, `code`, `link`, `list-item`. + +:::tip +More information about types can be found in . +::: \ No newline at end of file