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 by name (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 content is authored and rendered: rich-text (markdown), plain (escaped text), or html (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" /}}
  • id is authoritative: it identifies the referenced Object.
  • type is the Object type slug, used to pick a renderer.
  • slug is included for readability and resilience.
  • object and objects are 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/v3/buckets/:bucket_slug/blocks

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

GET
/v3/buckets/:bucket_slug/blocks
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-text stores markdown + block tokens instead, so render it with the patterns above rather than injecting raw HTML.