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#

typescript
body: { label: 'Body', format: 'richtext' },

Options#

OptionTypeDefaultDescription
requiredbooleanfalseMust be non-empty after trimming
hintstringHelper text below the field
richtext.embedsobjectEnable embeddable content (see below)
richtext.toolbarobjectAll enabledControl which toolbar buttons appear

Embeds#

typescript
body: {
  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 typeWhat it doesStored as
imagesInsert images from the media library<CmsImage mediaId="uuid" />
referencesEmbed other entries inline or as blocks<CmsRef id="post-456.json" display="block" />
conditionsA/B content branches<CmsCondition> with <CmsBranch> children
variablesDynamic placeholders<CmsVar name="user.firstName" />
componentsCustom React componentsStandard JSX tags

Toolbar#

typescript
richtext: {
  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.

typescript
const 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#

markdownrichtext
Storage.md companion file.mdx companion file
EmbedsNoneImages, references, conditions, variables, custom components
Query outputRaw markdown stringRichTextDocument AST
Public renderingMarkdownContentRichTextContent

For simple content without embeds, markdown is lighter and simpler.