Browse all documentation

Display rich text

Understand the JSON a rich-text field saves, and render it as HTML with Go, JavaScript, or Svelte.

A rich-text field saves JSON. Your frontend decides how it looks: render it with Go on the server, or with JavaScript or Svelte in your application. To let your app’s users write it too, see Use the editor in your app.

Understand the saved JSON

The saved field has a version and a root containing its text and other content. For example:

json
{
  "version": 1,
  "root": {
    "type": "root",
    "children": [
      {
        "type": "paragraph",
        "children": [
          { "type": "text", "text": "Hello, Ridu", "format": 1 }
        ]
      }
    ]
  }
}

Each object inside children is called a node. A paragraph contains text nodes; a list contains list items. Ridu checks these objects when saving and reports the path of invalid content, such as a link with no URL or a block type you have not configured.

Documents may contain at most 10,000 nodes and nest at most 64 levels deep. Unsupported document versions are rejected. Changing the document version requires a content migration.

Display rich text with Go

Use richtext.RenderDocument with a generated Go document type. It renders the built-in formatting and calls your function for custom blocks, so you can choose their HTML. The Go rendering example shows how to handle each generated block type.

If you are working with a raw store.Value, use richtext.RenderHTML. Supply functions for media, relationships, and custom blocks:

go
html, err := richtext.RenderHTML(
  value,
  map[string]func(store.Values) (string, error){
    "upload": func(node store.Values) (string, error) {
      return renderUploadReference(ctx, node)
    },
    "relationship": func(node store.Values) (string, error) {
      return renderRelationshipReference(ctx, node)
    },
    "block": func(node store.Values) (string, error) {
      return renderApplicationBlock(ctx, node)
    },
  },
)
if err != nil {
  return err
}

Built-in text is escaped, and links must be relative or use http, https, mailto, or tel. Your rendering functions must escape their own field values. Fetch related content through the local API so the user’s permissions still apply; do not treat a stored document ID as a URL.

Display rich text on your frontend

Fetch the document through your generated SDK, then pass its rich-text field to a renderer. For JavaScript or TypeScript, use renderRichTextHTML:

ts
import { renderRichTextHTML } from '@riducms/sdk/richtext';

const html = renderRichTextHTML(article.content);

Here, article.content is a saved rich-text value. Check it is present before rendering an optional field. Install only @riducms/sdk in a content consumer: it includes the document types and this renderer without Svelte, Lexical, or a browser.

For Svelte, import RichText from @riducms/plugin-richtext/svelte and render <RichText value={article.content} />. To display custom blocks, pass a blocks object that maps each block’s key to a Svelte component. Each component receives the fields for its block type. The Svelte rendering example shows a complete mapping.

The TypeScript equivalent maps keys to functions that return HTML. Use RichTextBlockRenderers to check that every block has a function, or RichTextBlockComponents for Svelte components. Missing renderers cause an error unless you provide a visible fallback. Renderers do not fetch related data; load it before rendering.

Show rich text as plain text

convertLexicalToPlaintext returns a document’s text without formatting, for previews, search, notifications or a language model. Blocks are separated by a blank line, each list item is on its own line, and a line break is a newline:

ts
import { convertLexicalToPlaintext } from '@riducms/sdk/richtext';

const preview = convertLexicalToPlaintext(article.content).slice(
  0,
  160
);

Custom blocks, uploads and relationships have no text of their own, so they’re left out. To include them, pass converters, as for renderRichTextHTML:

ts
const text = convertLexicalToPlaintext(article.content, {
  blocks: { callout: (block) => block.title ?? '' },
  nodes: { upload: (node) => node.caption ?? '' }
});

To go the other way, from Markdown to a document, use convertMarkdownToLexical.