@storyblok/richtext
@storyblok/richtext
is a custom resolver for Storyblok rich text in JavaScript applications.
Installation
Section titled “Installation”Add the package to a project by running this command in the terminal:
npm install @storyblok/richtext@latest
import { richTextResolver } from '@storyblok/richtext'; const { render } = richTextResolver(); const html = render(doc); document.querySelector<HTMLDivElement >('#app')!.innerHTML = ` <div>${html}</div> `;</HTMLDivElement>
Override resolvers
Section titled “Override resolvers”import { MarkTypes, richTextResolver } from '@storyblok/richtext'; const html = richTextResolver({ resolvers: { [MarkTypes.LINK]: (node) => { return `<button href="${node.attrs?.href}" target="${node.attrs?.target}"> ${node.children}</button>`; }, }, }).render(doc);
Resolvers context
Section titled “Resolvers context”You can access the resolver context via param when overriding a resolver. This give you access to:
originalResolvers
mergedResolvers
(after the overrides)- Internal
render
method
import { MarkTypes, richTextResolver } from '@storyblok/richtext';const html = richTextResolver({resolvers: { [BlockTypes.LIST_ITEM]: (node: StoryblokRichTextNode<string>, ctx) => { // Custom list item with enhanced styling const children = node.children || ''; return ctx.render('li', { class: 'custom-list-item', 'data-item': 'true' }, `<span class="bullet">→</span> ${children}`); },},}).render(doc);
Optimize images
Section titled “Optimize images”To optimize images
, use the optimizeImages
property on the richTextResolver
options. For a full list of available options, refer to the Image Service documentation.
import { richTextResolver } from "@storyblok/richtext";
const html = richTextResolver({ optimizeImages: { 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, }, },}).render(doc);
Markdown to Storyblok rich text
Section titled “Markdown to Storyblok rich text”The package now includes a powerful utility for converting Markdown content to Storyblok’s rich text format, which can be rendered with the existing richTextResolver
.
Supported markdown elements:
- Text formatting:
**bold**
,*italic*
,~~strikethrough~~
,`code`
,[links](url)
- Headings:
# H1
through###### H6
- Lists:
- unordered
and1. ordered lists
with nesting - Code blocks:
```fenced```
and indented blocks - Blockquotes:
> quoted text
- Images:

- Links:
[text](url)
and[text](url "title")
- Tables: Standard markdown table syntax
- Horizontal rules:
---
- Line breaks: (two spaces) for hard breaks
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);
// Convert to HTML using the existing richTextResolverconst html = richTextResolver().render(richtextDoc);document.getElementById("content").innerHTML = html;
Similar to overwriting resolves via rich text options, you can customize how specific Markdown elements are converted by providing custom resolvers:
import { markdownToStoryblokRichtext, MarkdownTokenTypes } from "@storyblok/richtext/markdown-parser";
const markdown = "# Custom Heading\nThis is a paragraph with [a link](https://example.com).";
const richtextDoc = markdownToStoryblokRichtext(markdown, { resolvers: { // Custom link resolver [MarkdownTokenTypes.LINK]: (token, children) => { return { type: "link", attrs: { href: token.attrGet("href"), title: token.attrGet("title") || null, target: "_blank", // Always open in new tab }, content: children, }; }, },});
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, which can be rendered with the existing richTextResolver
.
Supported HTML elements:
- Headlines:
<h1-6>
- Paragraphs:
<p>
- Lists:
<ol>
,<ul>
,<li>
- Tables:
<table>
,<thead>
,<tbody>
,<tr>
,<th>
,<td>
- Blockquote:
<blockquote>
- Code:
<pre>
,<code>
- Links:
<a>
- Formatting:
<strong>
,<b>
,<em>
,<i>
,<del>
,<s>
- Images:
<img>
- Misc:
<span>
,<hr>
,<br>
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);
// Convert to HTML using the existing richTextResolverconst html = richTextResolver().render(richtextDoc);document.getElementById("content").innerHTML = html;
Similar to overwriting resolves via richtext options, you can customize how specific HTML elements are converted by providing custom resolvers:
import { htmlToStoryblokRichtext, HTMLTags } from "@storyblok/richtext/html-parser";
const html = '<h1>Custom Heading\nThis is a paragraph with <a href="https://example.com">a link</a>.</h1>';
const richtextDoc = htmlToStoryblokRichtext(html, { resolvers: { // Custom link resolver [HTMLTags.A]: (node, content) => { return { type: "link", attrs: { href: node.attrs.href, title: node.attrs.title || null, target: "_blank", // Always open in new tab }, content, }; }, },});
Framework usage
Section titled “Framework usage”The @storyblok/richtext
package is framework-agnostic and can be used with any JavaScript-based frontend framework. Below are examples of how to use the package with different frameworks.
import React from 'react';import { richTextResolver } from '@storyblok/richtext';
const options: StoryblokRichTextOptions<ReactElement> = {renderFn: React.createElement,keyedResolvers: true,};
const html = richTextResolver(options).render(doc);
// Convert attributes in element to JSX. Refer to the `convertAttributesInElement` function in the playground/reactconst formattedHtml = convertAttributesInElement(html);
return <>{formattedHtml}</>;
Refer to playground/react in the @storyblok/richtext
package repository for a complete example.
<script setup> import type { VNode } from 'vue'; import { createTextVNode, h } from 'vue'; import { BlockTypes, richTextResolver, type StoryblokRichTextNode, type StoryblokRichTextOptions } from '@storyblok/richtext';
const options: StoryblokRichTextOptions<VNode> = { renderFn: h, textFn: createTextVNode, keyedResolvers: true, };
const root = () => richTextResolver<VNode>(options).render(doc);</script>
<template> <root /></template>
Refer to playground/vue in the @storyblok/richtext
package repository for a complete example.
TypeScript Generics
Section titled “TypeScript Generics”Correct type support in a framework-agnostic way is ensured by using Typescript Generics, circumventing the need to import types and require framework packages as dependencies.
Vanilla: string
Section titled “Vanilla: string”const options: StoryblokRichTextOptions<string> = {resolvers: { [MarkTypes.LINK]: (node: Node<string>) => { return `<button href="${node.attrs?.href}" target="${node.attrs?.target}">${node.children}</button>`; },},};
const html = richTextResolver < string > options.render(doc);
React: React.ReactElement
Section titled “React: React.ReactElement”const options: StoryblokRichTextOptions<React.ReactElement> = {renderFn: React.createElement,keyedResolvers: true,};const root = () => richTextResolver < React.ReactElement > options.render(doc);
Vue: VNode
Section titled “Vue: VNode”const options: StoryblokRichTextOptions<VNode> = {renderFn: h,keyedResolvers: true,};const root = () => richTextResolver < VNode > options.render(doc);
Further resources
Section titled “Further resources”Get in touch with the Storyblok community