---
title: @storyblok/richtext (Version 3.x)
description: @storyblok/richtext is a custom resolver for Storyblok rich text in JavaScript applications.
url: https://storyblok.com/docs/libraries/js/rich-text/v3
---

# @storyblok/richtext (Version 3.x)

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

## Installation

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

```bash
npm install @storyblok/richtext@3
```

## Usage

### Basic

```js
import { richTextResolver } from "@storyblok/richtext";
const { render } = richTextResolver();
const html = render(doc);
document.querySelector("#app").innerHTML = `<div>${html}</div>`;
```

### Override resolvers

```js
import { MarkTypes, richTextResolver } from "@storyblok/richtext";
const html = richTextResolver({
  resolvers: {
    [MarkTypes.LINK]: (node) => {
      return `
        <a class="my-custom-link" href="${node.attrs?.href}" target="${node.attrs?.target}">
          ${node.children}
        </a>
      `;
    },
  },
}).render(doc);
```

### 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

```ts
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

To optimize `images`, use the `optimizeImages` property on the `richTextResolver` options. For a full list of available options, refer to the [Image Service documentation](/docs/api/image-service/).

```js
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

_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

```js
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:

```js
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 heading resolver
    [MarkdownTokenTypes.HEADING]: (token, children) => {
      // Heading level is in token.tag (e.g., 'h1', 'h2', ...)
      const level = Number(token.tag.replace("h", ""));
      return {
        type: BlockTypes.HEADING,
        attrs: { level },
        content: children,
      };
    },
  },
});
```

## HTML to Storyblok rich text

_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>`

```js
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:

```js
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 H1 resolver
    [HTMLTags.H1]: (token, children) => {
      return {
        type: "heading",
        attrs: { level: 1 },
        content: children,
      };
    },
  },
});
```

## 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.

### React

> [!NOTE]
> For a better developer experience, use the corresponding APIs available in the framework SDK, such as the `StoryblokRichText` component in `@storyblok/react`. Learn more in the [@storyblok/react reference](/docs/libraries/js/react-sdk).

```tsx
import React from "react";
import { richTextResolver } from "@storyblok/richtext";

const options: StoryblokRichTextOptions<ReactElement> = {
  renderFn: React.createElement,
  keyedResolvers: true,
};

function Example({ doc }) {
  const html = richTextResolver(options).render(doc);
  return <>{formattedHtml}</>;
}
```

Refer to [playground/react](https://github.com/storyblok/richtext/blob/main/playground/react) in the `@storyblok/richtext` package repository for a complete example.

### Vue

> [!NOTE]
> For a better developer experience, use the corresponding APIs available in the framework SDK, such as the `StoryblokRichText` component in `@storyblok/vue`. Learn more in the [@storyblok/vue reference](/docs/libraries/js/vue-sdk).

```vue-html
<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](https://github.com/storyblok/richtext/blob/main/playground/vue) in the `@storyblok/richtext` package repository for a complete example.

## TypeScript Generics

Correct type support in a framework-agnostic way is ensured by using [Typescript Generics](https://www.typescriptlang.org/docs/handbook/2/generics.html), circumventing the need to import types and require framework packages as dependencies.

### Vanilla: `string`

```ts
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`

```ts
const options: StoryblokRichTextOptions<React.ReactElement> = {
  renderFn: React.createElement,
  keyedResolvers: true,
};
const root = () => richTextResolver<React.ReactElement>(options).render(doc);
```

### Vue: `VNode`

```ts
const options: StoryblokRichTextOptions<VNode> = {
  renderFn: h,
  keyedResolvers: true,
};
const root = () => richTextResolver<VNode>(options).render(doc);
```

## Further resources

[@storyblok/richtext HTML Sanitization Tutorial](https://storyblok.com/tp/rich-text-html-sanitization) Read the tutorial to learn how to sanitize the HTML string output of the rich text resolver.
