Structured document editor built from schema sections — headline, input, image, video fields, and nested body blocks. Content is stored and exported as JSON with type, meta, and sections.
Use when the editor should follow a fixed CMS schema instead of free-form HTML. Not compatible with the Versions plugin.
<script src="/assets/redactor/redactor.js"></script>
<script src="/assets/redactor/plugins/schema/schema.js"></script>
const app = Redactor('#entry', {
plugins: ['schema'],
image: {
upload: '/api/endpoint/upload-image/'
},
schema: {
schema: {
type: 'article',
meta: {
id: 'demo-1',
title: 'How to Write Texts'
},
sections: [
{
type: 'schemaSection',
data: {
id: 'headline',
label: 'Headline',
field: 'headline',
required: true,
content: 'How to Write Texts'
}
},
{
type: 'schemaSection',
data: {
id: 'cover',
label: 'Cover',
required: true,
field: 'image',
content: {
src: '/assets/img/redactor5-photo.jpg',
caption: 'Caption',
alt: 'Cover photo'
}
}
},
{
type: 'schemaSection',
data: {
id: 'author',
label: 'Author',
generated: true,
value: {
prefix: 'By',
name: 'John Smith'
}
}
},
{
type: 'schemaSection',
data: {
id: 'tags',
label: 'Tags',
field: 'input',
content: 'writing, tips, seo'
}
},
{
type: 'schemaSection',
data: {
id: 'body',
label: 'Body',
children: [
{
type: 'text',
data: {
content: '<p>Most texts on the internet go unnoticed. Readers skim the first few lines and leave within seconds — often because the text fails to communicate value quickly.</p>'
}
},
{
type: 'text',
data: {
content: '<p>A strong headline and clear opening paragraph help readers decide to stay. Edit the sections above and below to see schema fields in action.</p>'
}
}
]
}
}
]
}
}
});
object)
{ type, meta, sections } or an array of sections. Applied on first load when the editor has no content.string | false)
false<br> elements inside headline fields.string | false)
falseEach item in sections is a schemaSection block. The section mode depends on field:
| Mode | field |
Value property | Editable in editor |
|---|---|---|---|
| Headline | headline |
content (HTML) |
yes |
| Plain input | input |
content (string) |
yes |
| Image | image |
content (object) |
yes |
| Video | video |
content (object) |
yes |
| Body | omitted or blocks |
children (block array) |
yes |
| Generated | omitted | value (object) |
no (read-only) |
All section types support:
string) — stable section identifier (data-schema-id).string) — label shown in the editor chrome above the section.boolean) — marks the section as required in metadata (data-schema-required).boolean) — marks auto-generated content (data-schema-generated).string | string[]) — extra CSS classes on the <section> element (for example scheme-headline).Custom keys not listed above are preserved in section data and round-trip with JSON I/O. Use them for CMS-specific metadata (for example align, category, sortOrder).
Rich-text title field. Rendered as a large contenteditable area.
string) — HTML value. Plain text is fine; the editor stores inner HTML.<br>. Shift+Space inserts a non-breaking space.<br> elements inside headlines.{
type: 'schemaSection',
data: {
id: 'headline',
label: 'Headline',
required: true,
classname: 'scheme-headline',
field: 'headline',
content: 'How to Write Texts'
}
}
Single-line plain text field. Rendered as <input type="text">.
string) — current value.{
type: 'schemaSection',
data: {
id: 'tags',
label: 'Tags',
field: 'input',
content: 'writing, tips, seo'
}
}
Image field with upload area and metadata inputs.
object | string)
string) — image URL. A bare string is accepted as shorthand for { src: '…' }.string) — caption text.string) — alt text; synced to the preview image.image.* options: name, data, convertFormat, convertQuality).{
type: 'schemaSection',
data: {
id: 'cover',
label: 'Cover',
required: true,
field: 'image',
content: {
src: '/assets/photo-1.jpg',
caption: 'Caption',
alt: 'Cover photo'
}
}
}
Video embed field with live preview.
object | string)
string) — video URL or embed code. Parsed through the editor embed service (YouTube, Vimeo, direct <video> URLs, etc.). A bare string is accepted as shorthand.string) — caption metadata (stored in JSON; not rendered in the preview).iframe or <video>) is shown below the inputs.{
type: 'schemaSection',
data: {
id: 'video',
label: 'Video',
field: 'video',
content: {
src: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
caption: 'Intro video'
}
}
}
Nested content area — a regular block editor inside the section. Omit field or set field: 'blocks'.
array) — nested blocks (text, heading, list, image, etc.) in standard block JSON format.string | object) — block inserted when the section is empty. Default: { type: 'text', data: { content: '' } }. Pass a type string ('text') or { type, data }.boolean) — force editable mode when both value and children could apply. Default: editable when children is present.{
type: 'schemaSection',
data: {
id: 'body',
label: 'Body',
children: [
{
type: 'text',
data: { content: '<p>First paragraph.</p>' }
},
{
type: 'heading',
data: { level: 2, content: 'Section title' }
},
{
type: 'list',
data: {
items: [
{ content: 'Item one' },
{ content: 'Item two' }
]
}
}
]
}
}
Read-only display section for CMS-generated data. Set generated: true and provide value instead of content or children. The section is not editable.
object) — key/value pairs rendered as labeled chips. Each value is stored and output as HTML (inner HTML of the chip).prefix + name for author, publishedAt for dates, etc.).{
type: 'schemaSection',
data: {
id: 'author',
label: 'Author',
generated: true,
value: {
prefix: 'By',
name: 'John Smith'
}
}
}
{
type: 'schemaSection',
data: {
id: 'timestamp',
label: 'Published',
generated: true,
value: {
publishedAt: '2026-06-10T09:00:00Z'
}
}
}
A section with value and without children is always read-only. To switch a section from generated to editable body content, replace value with children.
JSON input/output wraps blocks in:
{
type: 'article',
meta: {
id: '123',
title: 'How to Write Texts',
authorId: 'user-1',
publishedAt: '2026-06-10T09:00:00Z',
updatedAt: null
},
sections: [ /* block JSON */ ]
}
This plugin does not emit its own events.
Loads a full schema document (type, meta, sections).
app.schema.setContent({
type: 'article',
meta: { id: '456' },
sections: [ /* … */ ]
});
Returns the current document as { type, meta, sections }.
const doc = app.schema.getContent();
Returns { type, meta } without sections.
const { type, meta } = app.schema.getSchema();
Updates type and meta without replacing section content.
app.schema.setSchema({ type: 'page', meta: { id: '789' } });