Rich text & blocks
The rich-text Metafield is the recommended field for long-form, formatted
content. It stores markdown prose interleaved with {{shortcode}} tokens
that reference reusable blocks. The API stores and returns the value
verbatim, your app resolves the tokens when it renders.
Value format
A rich-text value is a single string: markdown, plus zero or more block
tokens.
## Welcome
Some **rich** prose, then a reusable block:
{{ cta /}}
{{ name /}}references a block byname(its shortcode slug).- Whitespace inside the braces is flexible:
{{cta/}}and{{ cta /}}are equivalent. - Unknown tokens (no matching block definition) are preserved verbatim, never silently dropped.
- The reserved
{{ object ... /}}token references a live Cosmic Object inline (see Inline object embeds).
Blocks
Blocks are reusable content snippets defined per Bucket in the dashboard under
Settings -> Custom Blocks, and stored on the Bucket at
settings.content_blocks.
- Name
name- Type
- string
- Description
The block's shortcode slug, e.g.
cta. Referenced as{{ name /}}.
- Name
title- Type
- string
- Description
Human-friendly title shown in the editor.
- Name
description- Type
- string
- Description
Optional description shown in the editor.
- Name
content- Type
- string
- Description
The block body, authored as markdown (
rich-text), plain text (plain), or raw HTML (html).
- Name
editor- Type
- string
- Description
How
contentis authored and rendered:rich-text(markdown),plain(escaped text), orhtml(rendered verbatim).
Inline object embeds
In addition to blocks, a rich-text value can reference a live Cosmic Object
inline. Unlike a block (whose content lives in Bucket settings), an embed points
at a real Object by id, so it always reflects the latest version of that
Object. Authors insert embeds in the editor via the slash command Embed
object or the toolbar.
Embeds use a reserved, self-closing object shortcode:
Here is a related post:
{{ object type="posts" id="64f0c1abc1234567890abcde" slug="hello-world" /}}
idis authoritative: it identifies the referenced Object.typeis the Object type slug, used to pick a renderer.slugis included for readability and resilience.objectandobjectsare reserved and cannot be used as block names.
The API stores and returns embeds verbatim, just like any other part of the value. To render the referenced content, fetch the Object (optionally with relationship depth) and resolve the embed when you render (see Inline object embeds below).
Get blocks
Retrieve the Bucket's block definitions. This endpoint is read-key authed and
cached on the Bucket, so you can fetch it once and reuse the result across many
Objects. The same data is also available on the Bucket at
settings.content_blocks.
Required parameters
- Name
read_key- Type
- string
- Description
The Bucket read key.
Request
curl https://api.cosmicjs.com/v3/buckets/${BUCKET_SLUG}/blocks \
-d read_key=${BUCKET_READ_KEY} \
-G
Response
{
"blocks": [
{
"name": "cta",
"title": "Call to action",
"description": "Reusable CTA banner",
"content": "## Ready to start?\n\n[Get started](https://app.cosmicjs.com)",
"editor": "rich-text"
}
]
}
Rendering rich text
Fetch the Object (which carries the raw rich-text value) and the Bucket's
blocks, then resolve the tokens. The recommended pattern is to fetch blocks once
and reuse them.
import { createBucketClient } from '@cosmicjs/sdk'
const cosmic = createBucketClient({
bucketSlug: BUCKET_SLUG,
readKey: BUCKET_READ_KEY,
})
const { object: post } = await cosmic.objects
.findOne({ type: 'posts', slug: 'hello-world' })
.props('slug,title,metadata')
.depth(1)
const { blocks } = await cosmic.blocks.find()
Official React renderer
@cosmicjs/rich-text parses the
value, resolves blocks against their definitions, and renders to React.
Render with @cosmicjs/rich-text
import { RichText } from '@cosmicjs/rich-text'
export default function Post({ post, blocks }) {
return <RichText value={post.metadata.body} blocks={blocks} />
}
Each component receives { name, definition, contentHtml }, where contentHtml
is the block's content already rendered to HTML.
Rendering inline object embeds
Object embeds render control-first: you provide a React component per Object type
and a resolveObject function that returns the referenced Object. Fetch the
parent Object with relationship depth so the referenced Objects are available,
then look them up by id.
Render object embeds
import { RichText, ObjectBlockProps } from '@cosmicjs/rich-text'
const PostCard = ({ object }: ObjectBlockProps) => {
if (!object) return null
return (
<a href={`/posts/${object.slug}`} className="post-card">
{object.title}
</a>
)
}
export default function Post({ post, blocks }) {
// `post` was fetched with depth so related objects are embedded.
const related = (post.metadata?.related ?? []) as any[]
const byId = new Map(related.map((o) => [o.id, o]))
return (
<RichText
value={post.metadata.body}
blocks={blocks}
objects={{ posts: PostCard }}
resolveObject={({ id }) => byId.get(id)}
/>
)
}
Each object component receives { id, type, slug, object }. If you don't
register a component for a type, a default placeholder renders, the reference is
never silently dropped.
Bring your own renderer
If you want full control, render the value yourself: run the markdown through
your renderer of choice and replace each {{ name /}} token with your own
markup. The default @cosmicjs/rich-text renderer emits this wrapper per block:
<div class="cosmic-block cosmic-block-cta" data-block="cta">
<!-- block content rendered to HTML -->
</div>
For inline object embeds, parse the {{ object ... /}} token's id, type,
and slug attributes and render however you like. The default renderer emits a
reference placeholder when no component is provided:
<div class="cosmic-object" data-object="64f0c1abc1234567890abcde" data-object-type="posts">
posts: hello-world
</div>
Migrating from
html-textarea? The legacy Froala field stores raw HTML and returns it as a string.rich-textstores markdown + block tokens instead, so render it with the patterns above rather than injecting raw HTML.