Show navigationHide navigation
Rich Text
WYSIWYG editor with embeddable content blocks — images, references to other entries, conditional branches, template variables, and custom React components. Stored as companion .mdx files.
Usage#
typescriptbody: { label: 'Body', format: 'richtext' },
Options#
| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | Must be non-empty after trimming |
hint | string | — | Helper text below the field |
richtext.embeds | object | — | Enable embeddable content (see below) |
richtext.toolbar | object | All enabled | Control which toolbar buttons appear |
Embeds#
typescriptbody: { label: 'Body', format: 'richtext', richtext: { embeds: { images: true, references: { collections: ['post', 'author'], display: 'both' }, conditions: true, variables: ['user.firstName', 'site.name'], components: { CallToAction: { label: 'Call to Action', kind: 'block', props: [ { name: 'text', label: 'Button text', type: 'string', required: true }, { name: 'url', label: 'Link URL', type: 'url', required: true }, ], }, }, }, }, },
| Embed type | What it does | Stored as |
|---|---|---|
images | Insert images from the media library | <CmsImage mediaId="uuid" /> |
references | Embed other entries inline or as blocks | <CmsRef id="post-456.json" display="block" /> |
conditions | A/B content branches | <CmsCondition> with <CmsBranch> children |
variables | Dynamic placeholders | <CmsVar name="user.firstName" /> |
components | Custom React components | Standard JSX tags |
Toolbar#
typescriptrichtext: { toolbar: { formatting: true, // Bold, italic, underline headings: true, // Block type selector lists: true, // Ordered/unordered lists code: true, // Inline code codeBlock: true, // Code blocks links: true, // Links tables: true, // Tables thematicBreak: true, // Horizontal rule images: true, // Markdown images undoRedo: true, // Undo/redo }, },
Set any key to false to hide that toolbar section. Type / in the editor to open the slash command menu.
Storage#
Content is saved as a companion .mdx file alongside the entry JSON:
cms/content/post/ post-abc123.json # structured fields post-abc123.body.mdx # rich text content
Standard Markdown is valid MDX, so the format is backward-compatible with markdown fields.
Example: Landing page with custom CTAs#
Schema — a landing page where editors can insert call-to-action buttons and personalized greetings:
typescript// cms/octocms.config.ts landingPage: { label: 'Landing Page', fields: { title: { label: 'Title', format: 'string', entryTitle: true, required: true }, body: { label: 'Body', format: 'richtext', richtext: { embeds: { images: true, variables: ['user.name'], components: { CallToAction: { label: 'CTA Button', kind: 'block', props: [ { name: 'text', label: 'Button text', type: 'string', required: true }, { name: 'url', label: 'Link URL', type: 'url', required: true }, ], }, }, }, }, }, }, },
Query + render:
tsx// src/app/landing/page.tsx import { query } from 'cms/__generated__/query'; import { RichTextContent } from 'octocms/components/public'; function CallToAction({ text, url }: { text: string; url: string }) { return <a href={url} className="btn btn-primary">{text}</a>; } export default async function LandingPage() { const page = await query('landingPage').first(); if (!page) return null; return ( <main> <h1>{page.fields.title}</h1> <RichTextContent document={page.fields.body} components={{ CallToAction }} variables={{ 'user.name': 'Guest' }} /> </main> ); }
Query result#
query() reads the companion .mdx file, parses it to a RichTextDocument AST, and resolves embedded images and references.
typescriptconst page = await query('landingPage').first(); page.fields.body; // RichTextDocument { type: 'doc', content: [...] }
Render the AST with RichTextContent. See Rich Text guide for the full AST shape, custom component rendering, reference rendering, and migration from markdown.
Comparison with markdown#
markdown | richtext | |
|---|---|---|
| Storage | .md companion file | .mdx companion file |
| Embeds | None | Images, references, conditions, variables, custom components |
| Query output | Raw markdown string | RichTextDocument AST |
| Public rendering | MarkdownContent | RichTextContent |
For simple content without embeds, markdown is lighter and simpler.