Show navigationHide navigation
Markdown
Rich markdown editor powered by MDXEditor. Use for long-form content like blog posts, documentation articles, and changelogs.
Usage#
typescriptbody: { label: 'Body', format: 'markdown' },
Options#
| Option | Type | Default | Description |
|---|---|---|---|
required | boolean | false | Must be non-empty after trimming |
hint | string | — | Helper text below the field |
The editor toolbar includes bold / italic / underline, link, insert image, lists, and block type (heading levels, quote, etc.).
Inline images#
Click the image button in the toolbar to open the Insert image dialog. It hosts the same FormImageField component the entry editor uses for image-format fields, so the visual + interaction model is identical:
- Upload new image — opens the per-file upload queue (Title + "Generate blur placeholder" toggle). After upload, each file is inserted at the cursor in order.
- Select existing image — opens a compact media browser (search, folder filter, grid/list view) backed by the shared media list. Pick an asset and it's inserted with its Title as the alt text.
The dialog respects mediaAllowedFormats — files with disallowed extensions are skipped with a destructive toast.
The inserted markdown looks like:

Editing & deleting inline images#
Hovering an inserted image reveals a small floating toolbar with two icons:
- Pencil (Edit) — opens an alt-text editor seeded with the current value. Save updates the alt; the image source is locked (to swap the image, delete it and insert a new one).
- Trash (Delete) — opens a confirmation dialog before removing the image from the markdown body. The asset stays in the media library; you can re-insert it any time.
Drag-and-drop and paste-image still work and bypass the dialog entirely — they're routed through the package's imageUploadHandler, which uploads via the same uploadMedia action and inserts the /media/<uuid>.<ext> URL directly. This makes "drop a screenshot into the body" feel natural without an extra dialog hop.
This all works on the public site out of the box: MarkdownContent renders the syntax via react-markdown, and /media/<uuid>.<ext> is served by the media proxy route (src/app/media/[...slug]/route.ts) — so images committed to GitHub after deploy still resolve on Vercel's immutable filesystem.
Storage#
Markdown content is not stored inside the entry JSON. Instead, it is saved as a companion .md file alongside the entry:
cms/content/post/ post-abc123.json # structured fields (title, slug, etc.) post-abc123.body.md # markdown content for the "body" field
Naming convention: {collection}-{id}.{fieldName}.md
If a collection has multiple markdown fields, each gets its own companion file:
post-abc123.body.md post-abc123.summary.md
Example: Blog with posts#
Schema:
typescript// cms/octocms.config.ts post: { label: 'Post', hasMany: true, fields: { title: { label: 'Title', format: 'string', entryTitle: true, required: true }, slug: { label: 'Slug', format: 'slug', required: true }, publishedAt: { label: 'Published', format: 'datetime', dateOnly: true }, body: { label: 'Body', format: 'markdown', required: true }, }, },
Query + render:
tsx// src/app/blog/[slug]/page.tsx import { query } from 'cms/__generated__/query'; import { MarkdownContent } from 'octocms/components/public'; export default async function PostPage({ params }: { params: { slug: string } }) { const post = await query('post').filter({ slug: params.slug }).first(); if (!post) return null; return ( <article> <h1>{post.fields.title}</h1> <MarkdownContent content={post.fields.body} /> </article> ); }
MarkdownContent renders markdown to React using react-markdown + remark-gfm (tables, strikethrough, task lists) + rehype-sanitize (XSS protection).
Query result#
query() automatically reads the companion .md file and returns the raw markdown string in the field value. No manual file loading needed.
typescriptconst post = await query('post').first(); post.fields.body; // "# Hello\n\nThis is **bold** text."
Comparison with richtext#
markdown | richtext | |
|---|---|---|
| Storage | .md companion file | .mdx companion file |
| Editor | Basic toolbar | Full toolbar + slash commands + embeds |
| Embeds | None | Images, references, conditions, variables, custom components |
| Query output | Raw markdown string | RichTextDocument AST |
| Public rendering | MarkdownContent (react-markdown) | RichTextContent (AST → React) |
For content with embedded images, references, or custom components, use richtext instead.