Block editor for Strapi 5: an Editor.js custom field with Media Library uploads, custom blocks, version history, live collaboration across several servers and an optional AI assistant that uses your own provider. Free and open source under the MIT license.
Live docs: https://schero94.github.io/Magic-Editor-X/
- Features
- Screenshots
- Core concepts
- Screens
- Compatibility
- Install
- Configuration
- Live collaboration
- Running several instances
- AI assistant
- Data format
- Admin API and permissions
- Security and privacy
- Troubleshooting
- Migrating from 1.x
- Development and tests
- Maintenance status
- Languages
- License
Every feature is available in every installation. There is nothing to activate, no tiers and no usage limits, and the plugin makes no outgoing requests unless you use a feature that needs one (link previews, image by URL, AI assistant).
- Custom field
plugin::magic-editor-x.richtext(stored as JSON) for collection types, single types, components and dynamic zones, including localised fields and Draft & Publish. - Block tools (block name in brackets, the same names 1.x used):
- text: paragraph (
paragraph), heading H1 to H6 (header), list with nesting, ordered and unordered (list), checklist (checklist), quote (quote), warning (warning), alert in eight colours (alert), collapsible toggle (toggle), delimiter (delimiter), table (table); - code: plain code (
code), code with syntax highlighting (codeFlask; Prism with JavaScript, TypeScript, JSX/TSX, JSON, HTML, CSS/SCSS, PHP, Python, Ruby, Go, Rust, Java, Kotlin, Swift, C, C++, C#, SQL, Bash, YAML, Markdown, GraphQL, Docker and diff); - media: image upload by file or URL into the Media Library (
image), picking images from the Media Library (mediaLib, insertsimageblocks), image by URL without upload (simpleImage), file attachments (attaches), embeds such as YouTube, Vimeo, CodePen, GitHub, Figma and others (embed); - other: link preview card (
linkTool), person card (personality), call-to-action button (button), raw HTML (raw).
- text: paragraph (
- Inline tools: bold, italic, link with target and rel (
hyperlink), marker, underline, strikethrough, inline code, tooltip, and the AI assistant when it is enabled. - Block tunes: alignment (
alignmentTune), text variant call-out / citation / details (textVariant), indent (indentTune). - Editing: undo and redo, drag and drop of blocks, full screen mode, block and word count.
- Custom blocks: define your own blocks in Settings: simple blocks with fields (text, textarea, URL, number, select, checkbox, image, colour) and an optional HTML template, or embedded-entry blocks that link an entry of another content type. Import and export as JSON.
- Version history: save versions of a field, preview and restore them; automatic versions during live sessions.
- Live collaboration: several admins edit the same field at the same time with character-level merging, presence avatars and read-only viewers. Works with several Node processes behind a load balancer without sticky sessions.
- AI assistant: improve, fix grammar, shorten, expand, summarize, simplify, translate, change tone and continue writing, using Anthropic, OpenAI or any OpenAI-compatible server (for example a local model) with your own key.
- Strapi-native admin: built with the Strapi Design System, light and dark mode, keyboard accessible, admin in English, German, French, Spanish and Portuguese.
- Safe content: every value is normalised and sanitised on the server when it is saved, from the Content Manager, the Content API or code.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
All screenshots use synthetic data.
| Term | Meaning |
|---|---|
| Field | One Magic Editor X attribute of a content type or component. Its value is an Editor.js document. |
| Block | One entry of the document's blocks array, for example a paragraph or an image. Each block has an id, a type (the block name) and data. |
| Custom block | A block type defined in Settings > Magic Editor X > Custom blocks. Its name becomes the block type. |
| Version | A stored copy (snapshot) of one field of one document in one locale, listed in the version history. |
| Room | The live editing session of one field of one document in one locale. A room exists while people edit and for collaboration.roomTtlMinutes afterwards. |
| Viewer | Someone in a room who can read the document but not update it. Viewers see changes live but cannot type. |
| Screen | Where | Who can open it |
|---|---|---|
| Editor field | Content Manager, edit view | Everyone who can read the field. Uploads, version history and the AI assistant need the permissions in Admin API and permissions. |
| Version history | Clock button in the editor toolbar of a saved document | access and Content Manager read on the document; saving and restoring need update. |
| AI assistant | Inline toolbar (select text) | access and ai.use, when an administrator turned the assistant on. |
| Configuration | Settings > Magic Editor X > Configuration | settings.read (view), settings.update (edit). |
| Custom blocks | Settings > Magic Editor X > Custom blocks | custom-blocks.manage. |
| Plugin | Strapi | Node | Notes |
|---|---|---|---|
| 2.x | 5.38 or later (acceptance host: Strapi 5.61.1) | 22 or 24 | SQLite, PostgreSQL and MySQL/MariaDB through the Strapi database connection. Acceptance runs used SQLite and PostgreSQL 16. |
| 1.x | 5.x | 22 | No longer developed. |
The admin uses the React, React Router, react-intl, styled-components and Design System versions of the Strapi admin (React 18, Design System 2.2.4).
npm install magic-editor-xEnable the plugin in config/plugins.ts (or config/plugins.js):
export default ({ env }) => ({
'magic-editor-x': {
enabled: true,
config: {
// optional, see Configuration
},
},
});Then rebuild the admin and restart:
npm run build
npm run developAdd the field in the Content-Type Builder: Add another field > Custom > Magic Editor X. In its settings you can set a placeholder and a minimum height (120 to 2000 px).
After the first start, Super Admins can use everything. Other roles need the plugin actions listed in Admin API and permissions (Settings > Administration Panel > Roles > Plugins > Magic Editor X).
All keys are optional. Infrastructure options live in config/plugins; the AI provider is edited at runtime in Settings > Magic Editor X > Configuration.
| Key | Type | Default | Effect |
|---|---|---|---|
debug |
boolean | false |
Writes debug log lines (never content or personal data). |
rawHtml |
'sanitize' | 'allow' | 'disable' |
'sanitize' |
Raw HTML block on save: sanitize removes scripts, event handlers, iframes and unsafe URLs; allow stores the HTML as typed; disable empties it. |
sanitizeOnSave |
boolean | true |
Normalise and sanitise Magic Editor X values on every Document Service write. Turning this off is not recommended. |
encryptionKey |
string (32+ characters) | unset | Key for the stored AI provider key. Prefer the MAGIC_EDITOR_X_ENCRYPTION_KEY environment variable. |
uploads.maxImageBytes |
integer | 10485760 (10 MiB) |
Largest image accepted by file or URL (hard cap 500 MiB). |
uploads.maxFileBytes |
integer | 20971520 (20 MiB) |
Largest attachment. |
linkPreview.enabled |
boolean | true |
Link preview cards. When false, GET /magic-editor-x/link answers 404. |
linkPreview.timeoutMs |
integer | 8000 |
Time limit for fetching a page (1000 to 30000). |
api.autoParseJSON |
boolean | true |
Turn 1.x JSON strings into objects in Content API GET responses. |
ai.allowPrivateBaseUrl |
boolean | false |
Allow AI base URLs on loopback or private networks (for local models). Owner decision; not editable in the admin. |
ai.apiKey |
string | unset | Provider key from the environment. When set it wins over a key stored in the settings. |
ai.requestsPerMinute |
integer | 20 |
AI requests per admin per minute, counted per process. |
ai.timeoutMs |
integer | 60000 |
Time limit for one provider request. |
collaboration.enabled |
boolean | true |
Live collaboration and its Socket.IO server. |
collaboration.path |
string | '/magic-editor-x/realtime' |
Socket.IO path on the Strapi HTTP server. |
collaboration.allowedOrigins |
string[] | [] |
Extra browser origins allowed to open the socket. The admin URL and the server URL are always allowed. |
collaboration.pollIntervalMs |
integer | 400 |
How often each process reads updates written by other processes (50 to 10000). |
collaboration.roomTtlMinutes |
integer | 30 |
Keep a room's live state this long after the last person left. |
collaboration.maxMessageBytes |
integer | 2097152 (2 MiB) |
Largest accepted socket message. |
collaboration.snapshotIntervalMinutes |
integer | 10 |
Automatic version while people edit live. 0 turns it off. |
collaboration.tokenTtlSeconds |
integer | 60 |
Lifetime of the one-time token that opens the socket. |
history.maxSnapshotsPerField |
integer | 50 |
Versions kept per field and locale; older ones are deleted. |
The validator rejects wrong types and ranges at startup and names the key, never the value. Keys from 1.x that no longer exist are ignored with a warning; see Migrating from 1.x.
| Name | Required | Default | Effect |
|---|---|---|---|
MAGIC_EDITOR_X_ENCRYPTION_KEY |
Only to store an AI key in the settings | unset | 32 or more characters. Encrypts the AI provider key (AES-256-GCM). Without it the settings page cannot store a key; you can still pass one with ai.apiKey. Generate one with node -e "console.log(require('crypto').randomBytes(32).toString('base64'))". Keep it stable: a different key makes a stored provider key unreadable, and the settings page then asks you to enter it again. |
The plugin also uses Strapi's own admin.auth.secret (ADMIN_JWT_SECRET) to sign collaboration tokens. Every process of a cluster must use the same value.
Settings > Magic Editor X > Configuration stores these values in the Strapi store:
| Setting | Default | Range |
|---|---|---|
| Enabled | off | |
| Provider | anthropic |
anthropic, openai, openai-compatible |
| Base URL | provider default (https://api.anthropic.com, https://api.openai.com); required for openai-compatible |
https, or private hosts when ai.allowPrivateBaseUrl is on |
| Model | claude-opus-5-5 (Anthropic), gpt-6-luna (OpenAI) |
letters, digits and ._:/@- |
| API key | none | never returned; changing the provider or base URL requires entering it again |
| Max input characters | 8000 | 200 to 50000 |
| Max output tokens | 4096 | 64 to 16000 |
When a document has been saved once, the editor joins the room of its field. Everyone with Content Manager read access to that document, locale and field and the collaborate action can join; people with update access can type, others watch as viewers.
- Edits merge at character level when two people type in the same text field of a block, and per block otherwise. Every client and server converge on the same document (Yjs CRDT).
- Presence avatars show who is in the room; the badge shows the connection state (Connecting, Syncing, Live, Reconnecting).
- Changes made by others become part of your form. Save the entry as usual to store them.
- Text typed during a short network or server interruption is kept and sent after the reconnect.
- A new entry that has not been saved yet has no room.
- Access is checked again every five minutes; an admin who loses access is disconnected.
Collaboration works with several Node processes and several machines that share one database, for example Railway with 2 replicas and pm2 with 4 processes each, behind a load balancer without sticky sessions. No Redis is needed.
How it works:
- The browser connects with the WebSocket transport only. A WebSocket connection is one long-lived request, so it does not need sticky routing. Your proxy must allow WebSocket upgrades on
collaboration.path(default/magic-editor-x/realtime) and send them to Strapi. - The session token is signed with a key derived from
admin.auth.secret, so any process can accept a token issued by another one. All processes must use the sameADMIN_JWT_SECRET. - Every update is written to the table
magic_editor_x_collab_updatesbefore it is acknowledged. Other processes read new rows everycollaboration.pollIntervalMsand send them to their own sockets. - Presence is stored in
magic_editor_x_collab_presencewith heartbeats; leases and one-time keys inmagic_editor_x_locks. Seeding a room, compaction, automatic versions and cleanup each run once across all processes. - Clients resynchronise their state every 20 seconds and after each reconnect, so a process restart loses nothing that was acknowledged.
The plugin creates these three tables on startup. They are not content types, so Strapi's schema sync leaves them alone.
Behind a proxy, also set server.proxy in config/server (koa: true and the number of trusted hops in maxIpsCount) so Strapi sees the real client address.
The acceptance run with several processes is described in CONTRIBUTING.md.
The assistant is off until an administrator turns it on in Settings > Magic Editor X > Configuration and gives it a provider:
| Provider | Request | Default model (2026-11-11) |
|---|---|---|
| Anthropic | POST {base}/v1/messages, headers x-api-key and anthropic-version: 2026-10-21 |
claude-opus-5-5 |
| OpenAI | POST {base}/v1/chat/completions, Authorization: Bearer, max_completion_tokens |
gpt-6-luna |
| OpenAI-compatible (Ollama, vLLM, LM Studio and similar) | POST {base}/v1/chat/completions, optional bearer key, max_tokens |
none; you name the model |
- The key stays on the server. It is encrypted at rest with
MAGIC_EDITOR_X_ENCRYPTION_KEYor read fromai.apiKey, and it is never sent to the browser or written to logs. - The selected text (with its inline formatting) and a fixed instruction are sent to the provider. Nothing else from the document is sent. The text is never logged.
- Base URLs must be public https URLs. Loopback and private hosts are allowed only with
ai.allowPrivateBaseUrl: trueinconfig/plugins. Redirects are not followed. - Input is limited by "Max input characters", output by "Max output tokens". Each admin can send
ai.requestsPerMinuterequests per minute per process. - The answer is sanitised to the inline tags the editor uses before it reaches the browser. The admin reviews the suggestion and chooses Replace selection; nothing changes before that.
The field stores an Editor.js document as JSON:
{
"time": 1762866000000,
"version": "2.31.7",
"blocks": [
{ "id": "kq1x9w0abc", "type": "header", "data": { "text": "Release notes", "level": 2 } },
{
"id": "kq1x9w0abd",
"type": "paragraph",
"data": { "text": "Read the <a href=\"https://example.test\">guide</a>." },
"tunes": { "alignmentTune": { "alignment": "left" } }
}
]
}Data of each block type as stored after sanitising (shared/sanitize-policy.mjs):
| Block | data |
|---|---|
paragraph |
{ text } (inline HTML) |
header |
{ text, level: 1-6 } |
list |
{ style: 'ordered' | 'unordered' | 'checklist', meta?: { start?, counterType? }, items: [{ content, meta?, items: [...] }] } |
checklist |
{ items: [{ text, checked }] } |
quote |
{ text, caption, alignment: 'left' | 'center' } |
warning |
{ title, message } |
alert |
{ type: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'danger' | 'light' | 'dark', align: 'left' | 'center' | 'right', message } |
toggle |
{ text, status: 'open' | 'closed', fk?, items } (the next items blocks are its content) |
code |
{ code } (plain text) |
codeFlask |
{ code, language, showlinenumbers? } |
raw |
{ html } (see rawHtml) |
table |
{ withHeadings, stretched, content: string[][] } |
image |
{ file: { url, mime?, alt?, width?, height?, size?, formats?, id?, documentId? }, caption, withBorder, withBackground, stretched } |
simpleImage |
{ url, caption, withBorder, withBackground, stretched } |
linkTool |
{ link, meta: { title, description, site_name?, image?: { url } } } |
attaches |
{ file: { url, name?, extension?, size?, title? }, title } |
embed |
{ service, source, embed, width?, height?, caption } (embed is always https) |
personality |
{ name, description, link, photo } |
button |
{ text, link, style, openInNewTab } |
delimiter |
{} |
| custom simple block | { content, <field values>, blockType: 'simple', blockName } |
| custom embedded-entry block | { entry: { id, documentId, title, <display fields> }, contentType, displayMode } |
Inline HTML may contain b, strong, i, em, u, s, mark, code, a (href, target, rel, title), br, span (class cdx-tooltip with data-tooltip), sub and sup. Links allow http, https, mailto, tel and relative URLs. Escape plain-text fields (code, personality, button.text, link card text) when you render them.
Tunes are stored per block in tunes: alignmentTune: { alignment }, textVariant: '' | 'call-out' | 'citation' | 'details', indentTune: { indentLevel }.
The Content API returns the object as stored. Entries saved with 1.x may still contain a JSON string until they are saved again; with api.autoParseJSON (default) the plugin parses such strings in Content API GET responses.
| Action | Label in Roles | Gates |
|---|---|---|
plugin::magic-editor-x.access |
Use the editor (uploads, link previews, custom blocks, version history) | Editor configuration, link previews, uploads (with the Media Library permission), content type list, custom block list, version history, collaboration and AI routes |
plugin::magic-editor-x.collaborate |
Edit together in real time | Joining live rooms |
plugin::magic-editor-x.ai.use |
Use the AI assistant | AI requests |
plugin::magic-editor-x.custom-blocks.manage |
Manage custom blocks | Settings > Custom blocks and its routes |
plugin::magic-editor-x.settings.read |
Read settings | Settings > Configuration (read) |
plugin::magic-editor-x.settings.update |
Update settings | Saving settings, testing the AI connection |
Uploads also need the Media Library action plugin::upload.assets.create. Version history and collaboration also apply the Content Manager permissions of the document: read access (including conditions such as "is creator", locale and field permissions) to list versions and join a room, update access to save versions, restore and type. A document you cannot read answers 404, the same as a document that does not exist.
Super Admins have all actions. The access action keeps its 1.x id, so roles that had it keep using the editor.
All routes are admin routes served at /magic-editor-x/... and need an admin session (Authorization: Bearer <admin JWT>). There are no Content API routes. Errors use the Strapi envelope { "data": null, "error": { "status", "name", "message", "details" } }; validation errors carry details.fieldErrors keyed by field path. Editor.js tool routes answer { "success": 0, "message" } on errors, as the tools expect.
| Method | Path | Actions | Body / query | Response |
|---|---|---|---|---|
| GET | /magic-editor-x/config |
access | { data: { collaboration, ai, uploads, linkPreview, rawHtml, permissions } } |
|
| GET | /magic-editor-x/link |
access | ?url= |
{ success: 1, link, meta } |
| POST | /magic-editor-x/image/byFile |
access, upload | multipart image |
{ success: 1, file: { url, ... } } |
| POST | /magic-editor-x/image/byUrl |
access, upload | { url } |
{ success: 1, file } |
| POST | /magic-editor-x/file/upload |
access, upload | multipart file |
{ success: 1, file: { url, name, title, size, extension } } |
| GET | /magic-editor-x/content-types |
access | { data: [{ uid, kind, displayName, attributes }] } (types you can read) |
|
| GET | /magic-editor-x/custom-blocks |
access (?all=true also needs custom-blocks.manage) |
?all=true |
{ data: [block] } |
| GET | /magic-editor-x/custom-blocks/export |
custom-blocks.manage | { data: { format: 'magic-editor-x/custom-blocks', version: 2, exportedAt, blocks } } |
|
| POST | /magic-editor-x/custom-blocks/import |
custom-blocks.manage | export document | { data: { created: [name], skipped: [{ name, reason }] } } |
| PUT | /magic-editor-x/custom-blocks/order |
custom-blocks.manage | { ids } (every block once) |
{ data: [block] } |
| POST | /magic-editor-x/custom-blocks |
custom-blocks.manage | block definition | 201 { data: block } |
| PUT | /magic-editor-x/custom-blocks/:id |
custom-blocks.manage | full block definition | { data: block } |
| DELETE | /magic-editor-x/custom-blocks/:id |
custom-blocks.manage | { data: { id } } |
|
| POST | /magic-editor-x/custom-blocks/:id/duplicate |
custom-blocks.manage | 201 { data: block } |
|
| POST | /magic-editor-x/collaboration/session |
access, collaborate | { uid, documentId, locale?, field, initialValue? } |
{ data: { token, path, canEdit, expiresAt } } |
| GET | /magic-editor-x/history |
access | ?uid&documentId&locale&field |
{ data: [version], meta: { canRestore, canCreate } } |
| GET | /magic-editor-x/history/:id |
access | { data: { ...version, content } } |
|
| POST | /magic-editor-x/history |
access | { uid, documentId, locale?, field, content } |
201 new version, 200 with meta.created: false when unchanged |
| POST | /magic-editor-x/ai/:action |
access, ai.use | { text, language?, tone? } |
{ data: { text } } |
| GET | /magic-editor-x/settings |
settings.read | { data: { ai, environment } } (no secrets) |
|
| PUT | /magic-editor-x/settings |
settings.read, settings.update | { ai: { enabled, provider, baseUrl, model, maxInputChars, maxOutputTokens, apiKey? } } |
{ data: { ai, environment } } |
| POST | /magic-editor-x/settings/ai/test |
settings.read, settings.update | { ai: {...} } |
{ data: { ok: true } } |
upload means plugin::upload.assets.create. AI actions: improve, grammar, shorten, expand, summarize, simplify, translate (with language), tone (with tone), continue. Provider problems answer with name: 'AIProviderError' and details.code (DISABLED, NOT_CONFIGURED, NO_API_KEY, AUTH_FAILED, PROVIDER_RATE_LIMIT, TIMEOUT, REFUSED, PROVIDER_ERROR). Too long text answers 413, too many requests 429.
Example: save a version.
POST /magic-editor-x/history
Authorization: Bearer <admin JWT>
Content-Type: application/json
{ "uid": "api::article.article", "documentId": "k9d2w4xq8m1n0p3r5t7v9b1c", "locale": "en", "field": "body",
"content": { "blocks": [{ "id": "a1", "type": "paragraph", "data": { "text": "Hello" } }] } }{
"data": {
"id": "s8f2k1x0w9q7e5r3t1y6u4i2",
"version": 3,
"trigger": "manual",
"createdAt": "2026-11-11T09:30:00.000Z",
"createdBy": "Anna Beispiel",
"stats": { "blocks": 1, "words": 1, "characters": 5 },
"restorable": true
},
"meta": { "created": true }
}- Stored content: every Magic Editor X value is normalised and sanitised on save by a Document Service middleware (Content Manager, Content API, imports and code). Unknown keys of known block types are dropped, URLs are checked, inline HTML is limited to the tags above and the raw HTML block follows
rawHtml. Values over 5 MiB, with more than 5000 blocks or nested deeper than 24 levels are rejected with 400, never truncated. The admin sanitises again before rendering (DOMPurify with the same rules). - Outgoing requests: link previews and images by URL only fetch public http(s) addresses: the host is resolved once, every address must be public, the connection is pinned to that address, redirects are re-checked (at most 3), and size, type and time are limited. The AI provider is called only when an administrator configured it.
- Uploads are checked by their first bytes (not by the file name or the browser's MIME type) and stored through the Media Library with the admin as creator.
- Collaboration: tokens are signed, expire after
collaboration.tokenTtlSecondsand work once; the socket accepts the admin and server origins andcollaboration.allowedOrigins; messages are size-limited and must be valid Yjs updates; viewers cannot write. - Secrets: the AI key is encrypted with AES-256-GCM (random IV, versioned format) and never returned. Changing the provider or base URL requires entering the key again, so a stored key cannot be sent to another host.
- Logs contain operation names and error codes only, never content, URLs with queries, tokens, user ids or email addresses.
- Stored data: custom block definitions, versions (field content and the admin who saved it), live rooms (Yjs updates, removed
collaboration.roomTtlMinutesafter the last person left) and presence rows (admin display name, colour, caret position; removed when the socket closes or after missed heartbeats). - Content Security Policy: Editor.js and the Design System add inline styles, which Strapi's default
style-src 'unsafe-inline'allows. The socket connects to the same origin as the admin, whichconnect-src 'self'covers; add thewss:origin only when the admin runs on another host. Embed iframes needframe-srconly on your frontend.
Report vulnerabilities privately through GitHub security advisories on the repository.
| Symptom | Cause | Fix |
|---|---|---|
| The field shows "The editor could not be loaded." | The role lacks plugin::magic-editor-x.access, or the server is unreachable |
Grant the action; check the server log. |
| No upload or "You do not have permission to upload files" | The role lacks plugin::upload.assets.create |
Grant Media Library create to the role. |
| No version history (clock) button | The entry was never saved | Save the entry once. |
| Badge shows "Live editing unavailable" | No collaborate action, no read access to the document, or collaboration.enabled: false |
Grant the action or check the Content Manager permissions. |
| Badge stays at "Connecting" or "Reconnecting" | The proxy does not forward WebSocket upgrades on collaboration.path, or the origin is not allowed |
Allow upgrades on the path; add the admin origin to collaboration.allowedOrigins when the admin runs on another host. |
| Settings say the API key cannot be stored | MAGIC_EDITOR_X_ENCRYPTION_KEY is missing or shorter than 32 characters |
Set the variable and restart, or use ai.apiKey. |
| Settings say the stored key is unreadable | The encryption key changed | Enter the provider key again. |
| AI error "The AI provider rejected the key." | Wrong key for this provider | Enter the key again and use Test connection. |
Startup error [magic-editor-x] invalid configuration for: <key> |
Wrong type or range in config/plugins |
Fix the named key (see the reference table). |
| 400 "A Magic Editor X field contains invalid content" | The value is not editor JSON, too large or too deep | Send an Editor.js object within the limits. |
2.0 is a major release. Stored content keeps working; routes, permissions and configuration changed. The full guide is in the CHANGELOG. In short:
- Update the package and rebuild the admin:
npm install magic-editor-x@2 && npm run build. - Remove 1.x configuration keys (
enabledTools,linkPreviewTimeout,maxImageSize,maxFileSize,allowedImageTypes,collaboration.wsPath,wsUrl,sessionTTL,allowedAdminRoles,allowedAdminUserIds,cors) and use the new keys above. - Grant the new actions to your roles:
collaborate,ai.use,custom-blocks.manage,settings.read,settings.update. Collaboration grants from 1.x are replaced by Content Manager permissions; Strapi drops the old tables on the first start. - If you used the AI assistant, configure your own provider in Settings > Magic Editor X > Configuration.
- If your raw HTML blocks need scripts or iframes, set
rawHtml: 'allow'.
On the first start the plugin converts 1.x versions that only contained collaboration state and cleans custom block icons and templates. Both run once across all processes.
npm ci
npm run lint
npm run format:check
npm test # unit tests, no Strapi host needed
npm run build # admin and server bundles, then a runtime check
npm run verify
MEX_HOST_URL=http://127.0.0.1:1421 MEX_CREDS=/path/to/creds.json npm run test:integrationCONTRIBUTING.md explains the isolated acceptance host, the integration suite on SQLite and PostgreSQL and the multi-process acceptance run. AGENTS.md is the contract for coding agents.
Actively maintained. Releases are made with semantic-release from main; every release runs lint, tests, build, package verification and a production dependency audit first. Supported line: Strapi 5.
The admin is translated into English, German (formal Sie), French, Spanish and Portuguese.
MIT © 2025-2026 Schero D.








