---
url: /en/docs/api/rendering-markdown.md
description: >-
  Render the Markdown value of a RichText field in your own preview with the
  same pipeline as the Sveltia CMS preview pane, or convert it to HTML.
---

# Rendering Markdown

The value of a [RichText](/en/docs/fields/richtext) or [Markdown](/en/docs/fields/markdown) field is a Markdown string. When you render such a value yourself — in a [Custom Preview Template](/en/docs/api/preview-templates), the [preview output](/en/docs/api/editor-components#preview-output) of a [Custom Editor Component](/en/docs/api/editor-components), or a [Custom Field Type](/en/docs/api/field-types) — the string is used as is, so text like `**bold**` appears verbatim unless you convert it to HTML.

## Overview

Sveltia CMS offers two ways to do that. Both are available as soon as the CMS is loaded, whether you use the CDN build or the npm package, so no additional dependency is necessary:

* `CMS.renderRichText()` renders the value into a DOM element you provide, exactly like the built-in preview pane — custom editor components, images and sanitization included. Use it whenever you have an element to render into.
* `marked` and `DOMPurify` are the parser and sanitizer the CMS uses internally, exposed on the `window` object. Use them when you need an HTML string, such as for a React component.

## Using `renderRichText`

The `CMS.renderRichText()` method renders a Markdown string into a DOM element you provide, using the same pipeline as the preview pane of a RichText field:

* Custom editor components are rendered with their own `toPreview` output, including components nested in the value, recursively
* Markdown is parsed into HTML, with a single line break becoming a `<br>`
* Code blocks are syntax-highlighted
* Internal image paths are replaced with blob URLs, so uploaded images appear in the preview
* The resulting HTML is sanitized

It takes the target element, the Markdown string and an optional options object, and returns a function that removes the rendered content and destroys any component previews within it:

```js
const destroy = CMS.renderRichText(element, markdown, options);
```

The content is rendered asynchronously, and the target element doesn’t have to be attached to the document yet when you call the method — for example, an element created in `toPreview` that the CMS inserts into the preview pane afterwards.

The `options` object accepts the following property:

* `fieldConfig` — [RichText field](/en/docs/fields/richtext) options to be applied, such as [`editor_components`](/en/docs/fields/richtext#editor-components) to restrict the available components and [`sanitize_preview`](/en/docs/fields/richtext#sanitize-preview) to disable sanitization. Options not given here fall back to the [`field_defaults`](/en/docs/fields/richtext#global-field-defaults) configuration, except for `sanitize_preview`: the output is sanitized unless it’s explicitly set to `false` here.

The method is designed for an editor component whose `toPreview` returns a DOM element, where the value of a nested RichText field would otherwise be displayed verbatim. Call the returned `destroy` function once the CMS dispatches the `Unmount` event on the element, so that the nested previews are destroyed along with your component:

::: code-group

```js [Svelte]
import { registerEditorComponent } from '@sveltia/cms';
import { mount, unmount } from 'svelte';
import Warning from '$lib/components/Warning.svelte';

registerEditorComponent({
  id: 'warning',
  label: 'Warning',
  icon: 'warning',
  fields: [{ name: 'body', label: 'Body', widget: 'richtext' }],
  pattern: /<Warning>\s*(?<body>[\s\S]*?)\s*<\/Warning>/,
  toBlock: ({ body = '' }) => `<Warning>\n\n${body}\n\n</Warning>`,
  toPreview: ({ body = '' }) => {
    const element = document.createElement('div');
    const component = mount(Warning, { target: element, props: { body } });

    element.addEventListener('Unmount', () => unmount(component), { once: true });

    return element;
  },
});
```

```js [Vanilla JS]
CMS.registerEditorComponent({
  id: 'warning',
  label: 'Warning',
  icon: 'warning',
  fields: [{ name: 'body', label: 'Body', widget: 'richtext' }],
  pattern: /<Warning>\s*(?<body>[\s\S]*?)\s*<\/Warning>/,
  toBlock: ({ body = '' }) => `<Warning>\n\n${body}\n\n</Warning>`,
  toPreview: ({ body = '' }) => {
    const element = document.createElement('div');
    const destroy = CMS.renderRichText(element, body);

    element.className = 'bg-red-100';
    element.addEventListener('Unmount', destroy, { once: true });

    return element;
  },
});
```

:::

In the Svelte example, the component itself calls the method with an [attachment](https://svelte.dev/docs/svelte/@attach), which runs the returned `destroy` function automatically when the component is unmounted:

```svelte
<script>
  import { renderRichText } from '@sveltia/cms';

  let { body } = $props();
</script>

<div class="bg-red-100" {@attach (element) => renderRichText(element, body)}></div>
```

Because the nested value goes through the same component matching as the field itself, a component can contain other components, or even another instance of itself, as long as the [`pattern`](/en/docs/api/editor-components#required-properties) of each component can match its own block within the parent’s. Any nested component preview is rendered in place, whether it returns a string, a DOM element or a React element.

::: tip Raw values for nested fields

The value of a nested RichText field is passed to `toPreview` as is, including the syntax of any component within it. It’s never partially rendered, regardless of the order in which components are registered, so you can always pass it to `renderRichText` or process it yourself.

:::

## Using `marked` and `DOMPurify`

If you need an HTML string rather than a rendered element — for example, to pass it to a React component’s `dangerouslySetInnerHTML` prop — Sveltia CMS exposes the two libraries it uses internally, so you don’t need to add a dependency of your own:

* `marked` — The [Marked](https://marked.js.org/) parser, which converts a Markdown string to an HTML string
* `DOMPurify` — The [DOMPurify](https://github.com/cure53/DOMPurify) sanitizer, which strips scripts and other dangerous markup from an HTML string

These are available on the `window` object when Sveltia CMS is loaded, whether you use the CDN build or the npm package. No additional imports are necessary to use them.

```js
const html = DOMPurify.sanitize(marked.parse(markdown));
```

The CMS renders the preview pane with the `breaks` option enabled, meaning a single line break becomes a `<br>`. Pass the same option if you want your output to match:

```js
const html = DOMPurify.sanitize(marked.parse(markdown, { breaks: true }));
```

Note that, unlike `renderRichText`, this approach doesn’t render custom editor components or resolve internal image paths; the value is converted as plain Markdown.

::: danger Security Risk

Always sanitize the HTML before inserting it into the DOM, as the examples above do. Markdown allows raw HTML, so skipping the sanitizer can expose your CMS to [cross-site scripting](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS) (XSS) attacks if untrusted users have access to the CMS, especially when using [Open Authoring](/en/docs/workflows/open), because entries can be written by anybody.

:::

::: warning Shared parser instance

`marked` is the very parser the CMS uses to render the preview pane, so any extension you add with [`marked.use()`](https://marked.js.org/using_pro) also changes how the CMS itself renders Markdown. Prefer passing [options](https://marked.js.org/using_advanced) to `marked.parse()` for one-off customization.

:::
