Magic-Editor-X — API Reference
Reading content
Standard Strapi REST:
bash
GET /api/articles/1?populate=*json
{
"data": {
"id": 1,
"attributes": {
"title": "My article",
"content": {
"time": 1712000000000,
"blocks": [
{ "type": "header", "data": { "text": "Intro", "level": 1 } },
{ "type": "paragraph", "data": { "text": "Hello <b>world</b>." } }
],
"version": "2.x"
}
}
}
}The content field is stored as JSON. Render it on your frontend — see below.
Writing content
Create:
bash
POST /api/articles
Content-Type: application/json
Authorization: Bearer <jwt>
{
"data": {
"title": "New article",
"content": {
"blocks": [
{ "type": "paragraph", "data": { "text": "Drafted programmatically." } }
]
}
}
}Update: same as Strapi (PUT /api/articles/:id). The editor in the admin UI will show the updated blocks on next open.
GraphQL
graphql
query Article($id: ID!) {
article(id: $id) {
data {
attributes {
title
content
}
}
}
}The content scalar is serialized as JSON in the response.
Rendering on a frontend
React (basic)
tsx
import DOMPurify from 'dompurify';
export function EditorContent({ content }) {
return (
<div className="editor-content">
{content.blocks.map((block, i) => renderBlock(block, i))}
</div>
);
}
function renderBlock(block, i) {
const sanitize = (html) => ({ __html: DOMPurify.sanitize(html) });
switch (block.type) {
case 'header': {
const Tag = `h${block.data.level}`;
return <Tag key={i} dangerouslySetInnerHTML={sanitize(block.data.text)} />;
}
case 'paragraph':
return <p key={i} dangerouslySetInnerHTML={sanitize(block.data.text)} />;
case 'list':
return (
<block.data.style === 'ordered' ? 'ol' : 'ul'>
{block.data.items.map((item, j) => (
<li key={j} dangerouslySetInnerHTML={sanitize(item)} />
))}
</>
);
case 'image':
return <img key={i} src={block.data.url} alt={block.data.alt || ''} />;
case 'code':
return <pre key={i}><code>{block.data.code}</code></pre>;
case 'quote':
return <blockquote key={i}><p dangerouslySetInnerHTML={sanitize(block.data.text)} /><cite>{block.data.caption}</cite></blockquote>;
case 'delimiter':
return <hr key={i} />;
default:
return null;
}
}Using editorjs-react-renderer
bash
npm install editorjs-react-renderertsx
import Output from 'editorjs-react-renderer';
export function EditorContent({ content }) {
return <Output data={content} />;
}HTML renderer (SSG / SSR)
bash
npm install editorjs-htmltypescript
import edjsHTML from 'editorjs-html';
const edjsParser = edjsHTML();
const html = edjsParser.parse(content).join('');
// Insert into your templateCustom renderers per block type
tsx
import Output from 'editorjs-react-renderer';
const renderers = {
image: ({ data }) => (
<figure>
<img src={data.url} alt={data.alt} loading="lazy" />
{data.caption && <figcaption>{data.caption}</figcaption>}
</figure>
),
code: ({ data }) => (
<pre className={`language-${data.language}`}>
<code>{data.code}</code>
</pre>
),
};
<Output data={content} renderers={renderers} />Strapi service API
typescript
// Server-side — inside your plugin/controller
const parser = strapi.plugin('magic-editor-x').service('parser');
// Convert blocks -> HTML
const html = parser.toHtml(article.content);
// Convert blocks -> plain text (for search indexing, excerpts)
const text = parser.toText(article.content);
// Convert Markdown -> blocks (for migration)
const blocks = parser.fromMarkdown('# Title\n\nHello');Custom block registration
typescript
// src/index.ts
export default {
register({ strapi }) {
strapi.plugin('magic-editor-x').registerBlock({
type: 'callout',
title: 'Callout',
toolbox: { title: 'Callout', icon: '💡' },
renderer: {
class: CalloutTool, // Editor.js tool class
config: { /* defaults */ },
},
});
},
};See Editor.js tool documentation for the tool class interface.
Search indexing
For fulltext search (Elasticsearch, Algolia, Meilisearch), index the plain-text version:
typescript
// src/api/article/services/article.ts
export default {
async afterUpdate(event) {
const parser = strapi.plugin('magic-editor-x').service('parser');
const text = parser.toText(event.result.content);
await searchClient.index({
id: event.result.id,
title: event.result.title,
body: text,
});
},
};Next: Examples →