@storyblok/richtext (Version 5.x)
@storyblok/richtext is a custom renderer for Storyblok rich text content in JavaScript applications.
Installation
Section titled “Installation”Add the package to a project by running this command in the terminal:
npm install @storyblok/richtext@latestimport { renderRichText } from "@storyblok/richtext";
const html = renderRichText(document);document.querySelector("#app").innerHTML = `<div>${html}</div>`;Supported nodes and marks
Section titled “Supported nodes and marks”The following nodes and marks are supported by the rich text renderer and parser APIs.
docparagraphheadingblockquoteordered_listbullet_listlist_itemhorizontal_rulehard_breakcode_blockimageemojitabletableRowtableCelltableHeaderblok
bolditalicunderlinestrikecodelinksuperscriptsubscripthighlighttextStyleanchorstyled
Custom renderers
Section titled “Custom renderers”Rendering can be customized by providing renderer functions for individual nodes and marks. When using TypeScript, all supported nodes and marks are fully type-safe.
Add a custom renderer
Section titled “Add a custom renderer”Create a custom renderer as demonstrated in the example below:
import { renderRichText } from "@storyblok/richtext";
const options = { renderers: { paragraph: ({ content, context }) => `<p class="my-paragraph">${renderRichText(content, context)}</p>`, heading: ({ children, attrs }) => { const level = attrs.level; return `<h${level} class="my-heading">${children}</h${level}>`; }, bold: ({ children }) => `<strong class="my-bold">${children}</strong>`, },};
const html = renderRichText(document, options);The following properties are available to renderer functions.
Node renderers
attrschildrencontentmarkscontext
Mark renderers
attrschildren
For nodes, such as paragraphs or headings, either the rendered children value can be used directly or the raw content can be accessed.
The context object provides access to the options, allowing child nodes to be rendered recursively when required.
The example above demonstrates both approaches.
Blok component rendering
Section titled “Blok component rendering”Storyblok components are embedded in rich text as blok nodes. Each blok node contains an attrs.body array with one or more Storyblok components that must be rendered manually.
import type { SbRichTextProps, renderRichText } from "@storyblok/richtext";
const MyBlokRenderer = ({ attrs }: SbRichTextProps<"blok">) => { const body = Array.isArray(attrs?.body) ? attrs.body : [];
return body.map((blok) => `<button data-uid="${blok._uid}">${blok.title}</button>`).join("");};
const html = renderRichText(document, { renderers: { blok: MyBlokRenderer, },});Custom table rendering
Section titled “Custom table rendering”When creating custom table renderers, the splitTableRows helper can be used to separate header rows from body rows.
import { renderRichText, splitTableRows, type SbRichTextRenderContext } from "@storyblok/richtext";
const options: SbRichTextRenderContext = { renderers: { table: ({ content, context }) => { const { headerRows, bodyRows } = splitTableRows(content); return ` <table class="custom-table"> ${headerRows ? `<thead>${renderRichText(headerRows, context)}</thead>` : ""} <tbody>${renderRichText(bodyRows, context)}</tbody> </table> `; }, },};const html = renderRichText(document, options);The splitTableRows helper separates table rows into two collections:
headerRowscontains rows with header cells (table_header).bodyRowscontains all remaining rows.
This helper is framework-agnostic and can be used in any custom renderer implementation, including framework SDK integrations such as @storyblok/vue, @storyblok/react, @storyblok/astro, and @storyblok/svelte.
Optimize images
Section titled “Optimize images”To optimize images, use the optimizeImage property on the renderRichText options. For a full list of available options, refer to the Image Service documentation.
import { renderRichText } from "@storyblok/richtext";
const html = renderRichText(document, { optimizeImage: { class: "my-peformant-image", loading: "lazy", width: 800, height: 600, srcset: [400, 800, 1200, 1600], sizes: ["(max-width: 400px) 100vw", "50vw"], filters: { format: "webp", quality: 10, grayscale: true, blur: 10, brightness: 10, }, },});TypeScript types
Section titled “TypeScript types”The package exports a set of TypeScript types that can be used when building custom renderers integrations.
import type { SbRichTextDoc, SbRichTextNode, SbRichTextMark, SbRichTextRenderContext, SbRichTextRendererMap, SbRichTextProps, SbRichTextImageOptions,} from "@storyblok/richtext";Available types
Section titled “Available types”| Type | Description |
|---|---|
SbRichTextDoc | The root rich text document type. |
SbRichTextNode | Union type representing all supported rich text nodes. |
SbRichTextMark | Union type representing all supported rich text marks. |
SbRichTextRenderContext | Rendering context passed to node renderers. |
SbRichTextRendererMap | Type used when defining custom renderer mappings. |
SbRichTextProps | Renderer props for all supported nodes and marks. |
SbRichTextImageOptions | Configuration options for image optimization. |
HTML to Storyblok rich text
Section titled “HTML to Storyblok rich text”The package includes a utility for converting HTML content to Storyblok’s rich text format.
import { htmlToStoryblokRichtext } from "@storyblok/richtext/html-parser";
const html = `<h1>Main Heading</h1>
<p>This is a <strong>bold</strong> paragraph with <em>italic</em> text.</p>
<ul> <li>List item 1</li> <li>List item 2</li></ul>
<blockquote> <p>This is a blockquote</p></blockquote>`;
const richtextDoc = htmlToStoryblokRichtext(html);
const html = renderRichText(richtextDoc);document.getElementById("content").innerHTML = html;Custom HTML parsers
Section titled “Custom HTML parsers”Parsing behavior can be customized by providing custom parsers.
const html = `<div class="content"> <p> This is a simple paragraph with <strong>bold text</strong>, <em>italic text</em>, and a <a href="https://example.com">link to Example</a>. </p>
<p> You can also place a link in between a sentence, like <a href="https://astro.build">Astro</a>, which is a great framework for building fast websites. </p>
<div class="card"> <h2>Getting Started</h2> <p> This card contains a heading and a short description to demonstrate nested content structure inside a container element. </p> </div></div>`;const document = htmlToStoryblokRichtext(html, { parsers: { blok: { parseHTML: () => [{ tag: "div.card" }], attributeParsers: { body: (el) => { const title = el.querySelector("h2")?.textContent ?? ""; const description = el.querySelector("p")?.textContent ?? ""; return [ { title, description, component: "card", }, ]; }, }, }, },});As with rich text rendering, full type safety is provided for all supported nodes and marks when creating custom parsers. Each supported parser can define the following options:
parseHTML- Defines one or more parsing rules used to identify matching HTML elements.attributeParsers- Defines how attributes should be extracted and mapped to rich text node attributes.
In the previous example, a div element with the card class is mapped to a Storyblok component. The parser extracts the title and description from the HTML structure and stores them in the body attribute of the resulting blok node.
Link parser example
Section titled “Link parser example”The following example demonstrates how to override the default link parser, allowing you to customize how link elements are converted into Storyblok rich text link attributes.
import { htmlToStoryblokRichtext, mapToAttribute } from "@storyblok/richtext/html-parser";
const html = `<a href="/documentation/getting-started" target="_self" title="View the Getting Started guide" aria-label="Navigate to the Getting Started guide"> Getting Started</a>`;const result = htmlToStoryblokRichtext(html, { parsers: { link: { attributeParsers: { href: mapToAttribute("href"), target: mapToAttribute("target"), linktype: (el) => { const href = el.getAttribute("href") || ""; if (href.startsWith("http")) { return "url"; } else if (href.startsWith("mailto:")) { return "email"; } else if (href.startsWith("/")) { return "story"; } return null; }, custom: (el) => ({ title: mapToAttribute("title")(el), ariaLabel: mapToAttribute("aria-label")(el), }), }, }, },});The mapToAttribute helper is provided for mapping HTML attributes directly to rich text node attributes. This can significantly reduce boilerplate when multiple attributes need to be extracted.
Markdown to rich text
Section titled “Markdown to rich text”The package includes a utility for converting Markdown content to Storyblok’s rich text format.
import { markdownToStoryblokRichtext } from "@storyblok/richtext/markdown-parser";
const markdown = `# Main Heading
This is a **bold** paragraph with *italic* text.
- List item 1- List item 2
> This is a blockquote`;
const richtextDoc = markdownToStoryblokRichtext(markdown);
const html = renderRichText(richtextDoc);document.getElementById("content").innerHTML = html;Markdown parsing can be customized by providing parser overrides. For example, to parse all headings as level 5:
import { markdownToStoryblokRichtext } from "@storyblok/richtext/markdown-parser";
const richtextDoc = markdownToStoryblokRichtext(markdown, { parsers: { heading: { attributeParsers: { level: (el: HTMLElement) => 5, }, }, },});Framework usage
Section titled “Framework usage”The @storyblok/richtext package is framework-agnostic and can be used with any JavaScript frontend framework. The package itself returns an HTML string.
For framework-specific rendering, a StoryblokRichText component is provided by each SDK. These components integrate with the framework’s rendering system and provide an improved developer experience when rendering rich text content.
Further resources
Section titled “Further resources”Previous versions
Section titled “Previous versions”Was this page helpful?
This site uses reCAPTCHA and Google's Privacy Policy (opens in a new window) . Terms of Service (opens in a new window) apply.
Get in touch with the Storyblok community