Skip to content

Rich Text

WollyCMS stores rich text as TipTap JSON, not raw HTML. The @wollycms/astro package includes renderRichText() to convert this JSON to HTML at render time.

The simplest way to render rich text is with the built-in component:

---
import RichText from '@wollycms/astro/components/RichText.astro';
const { fields } = Astro.props;
---
<div class="prose">
<RichText content={fields.body} />
</div>

Or call renderRichText() directly for more control:

---
import { renderRichText } from '@wollycms/astro/helpers/richtext';
const { fields } = Astro.props;
const html = renderRichText(fields.body);
---
<div class="prose" set:html={html} />

renderRichText() handles these TipTap node types out of the box:

Node typeHTML output
paragraph<p>
heading<h2><h6> (based on level attribute)
bulletList<ul>
orderedList<ol>
listItem<li>
blockquote<blockquote>
codeBlock<pre><code> (with optional language class)
image<img> (or <a><img></a> when linked)
table<table> with <tr>, <th>, <td>
horizontalRule<hr />
hardBreak<br />

Inline formatting is stored as marks on text nodes:

MarkHTML output
bold<strong>
italic<em>
underline<u>
strike<s>
code<code>
link<a href="...">
subscript<sub>
superscript<sup>

Image nodes support these attributes:

AttributeTypeDescription
srcstringImage URL
altstringAlt text
titlestringTitle text (optional)
widthstringCSS width (e.g. "50%")
floatstring"none", "left", "right", or "center"
captionstringFigure caption text (optional)
hrefstringLink URL — wraps the image in <a> (optional)
linkTargetstringLink target, e.g. "_blank" (optional)

When an image has an href, the rendered output wraps it in a link:

<!-- Image without link -->
<img src="/api/content/media/42/original" alt="QR code" />
<!-- Image with link -->
<a href="https://example.com" target="_blank">
<img src="/api/content/media/42/original" alt="QR code" />
</a>

Editors can add links to images in the admin UI by clicking the image and then clicking the link button in the toolbar.

If you write your own renderer instead of using renderRichText(), make sure to handle image links. The href and linkTarget values are stored as attributes on the image node, not as marks:

{
"type": "image",
"attrs": {
"src": "/api/content/media/42/original",
"alt": "QR code",
"href": "https://example.com",
"linkTarget": "_blank"
}
}

Rich text content is stored as a JSON document with a doc root node. Here is an example showing the structure:

{
"type": "doc",
"content": [
{
"type": "heading",
"attrs": { "level": 2 },
"content": [
{ "type": "text", "text": "Welcome" }
]
},
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Visit our " },
{
"type": "text",
"text": "website",
"marks": [
{ "type": "link", "attrs": { "href": "https://example.com" } }
]
},
{ "type": "text", "text": " for details." }
]
},
{
"type": "image",
"attrs": {
"src": "/api/content/media/5/original",
"alt": "Logo",
"href": "https://example.com",
"linkTarget": "_blank"
}
}
]
}