Skip to content

@storyblok/richtext

@storyblok/richtext is a custom resolver for Storyblok rich text in JavaScript applications.

Add the package to a project by running this command in the terminal:

Terminal window
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>
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);

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);

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);
Introduced in 3.6.0

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 and 1. ordered lists with nesting
  • Code blocks: ```fenced``` and indented blocks
  • Blockquotes: > quoted text
  • Images: ![alt](src "title")
  • 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 richTextResolver
const 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,
};
},
},
});
Introduced in 3.7.0

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 richTextResolver
const 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,
};
},
},
});

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/react
const 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.

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.

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);
const options: StoryblokRichTextOptions<React.ReactElement> = {
renderFn: React.createElement,
keyedResolvers: true,
};
const root = () => richTextResolver < React.ReactElement > options.render(doc);
const options: StoryblokRichTextOptions<VNode> = {
renderFn: h,
keyedResolvers: true,
};
const root = () => richTextResolver < VNode > options.render(doc);