Rich Text
Learn how to work with rich text from Prismic.
Prismic provides the tools to write, save, query, and render rich text content.
Rich text is text that can be formatted in a user interface. Prismic provides a rich text editor that saves the content in a simple data structure designed for web publishing.
Most people are comfortable creating content in a rich text editor. But rich text from an application like Microsoft Word or Google Docs has very complicated encoding that can be difficult to work with.
Prismic stores text in a particular JSON format that we call "structured text," which is portable and adaptable. Other content management systems often store text content as HTML or Markdown, like this:
- HTML
- Markdown
"blog_post_content": "<div>I started reading <em>Great Expectations</em>!</div>"
"blog_post_content": "I started reading _Great Expectations_!"
HTML or Markdown can be convenient at first, but they will become restrictive over time. To customize the markup or the content, you need to add complicated logic. Prismic’s rich text format is designed to allow convenient customizations.
Here is what rich text looks like on the Prismic API:
"blog_post_content": [
{
"type": "paragraph",
"text": "I started reading Great Expectations!",
"spans": [
{
"start": 18,
"end": 36,
"type": "em"
}
]
}
]
Rich text is presented as an array. Each item in the array is a block of text (a block-level element like a heading, paragraph, or preformatted). Each item contains the raw text string, a block type, and some meta information that describes the text. The meta-information will describe the inline elements, like emphasis, strong, and links.
Here are all the properties of rich text:
type
string
The type of block-level formatting. Values: "paragraph"
, "o-list-item",
"list-item"
, "heading1"
, "heading2"
, "heading3"
, "heading4"
, "heading5"
, "heading6"
, "preformatted"
, "image"
, and "embed"
.
text
string
The text content of the element. Example: "Lorem ipsum"
.
direction
string
A string that specifies the direction of text only if right-to-left formatting is applied. Value: "rtl"
.
spans
array
An array that describes inline formatting.
spans[index]
object
A single span of inline formatting.
spans[index].start
number
The zero-indexed position of the start of a span (inclusive). Example: 18
.
spans[index].end
number
The position of the final character at the end of a span. Example: 36
.
spans[index].type
string
The type of inline formatting. Values: "label"
, "em"
, and "hyperlink"
.
spans[index].url
string
The URL of a media item if the element is an image.
spans[index].alt
string
The alt text of an image if the element is an image.
spans[index].copyright
string
The copyright of an image if the element is an image.
spans[index].dimensions
object
An object that contains the dimensions of an image if the element is an image.
spans[index].dimensions.width
number
The width of an image if the element is an image.
spans[index].dimensions.height
number
The height of an image if the element is an image.
spans[index].data
object
An object that contains additional data if the element is a label or hyperlink. See the rich text labels section and fields article for more information.
spans[index].oembed
object
An object that contains additional data if the element is an embed. See the fields article for more information.
It is still possible to edit and publish HTML and Markdown with Prismic. To do so, configure a rich text field with only the pre
element enabled. Your content editors will see a monospace editor. Use the helper methods from Prismic's open-source packages to convert the field to plain text in your app. Then, you'll have basic HTML or Markdown, which you can use as needed in your website.
There are three text fields available for the Prismic editor:
- Key text
- Titles
- Rich text
Key text is the only field that does not use structured text. It is for short, unformatted text. In the JSON API response, key text is delivered as a simple primitive string:
{
"example_key_text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
}
The rich text and title fields are both structured text. The title field has two unique restrictions:
- The element must be a heading
- It only allows one block of text
The rich text field has no restrictions. You can customize the rich text field that appears in the Prismic Editor with the following element types:
- Heading elements (h1, h2, h3, h4, h5, h6)
- Normal text paragraph
- Strong or bold text
- Emphasized or italic text
- Preformatted text
- Inline hyperlink to the web, to a document, or a media item
- Image
- Embed to a service URL supporting oEmbed
- Unordered list
- Ordered list
- Right-to-left text
- Custom labels
To learn more about how to write rich text, read Edit Rich Text.
Use key text for a simple piece of metadata, such as key terms. Use the title field for titles and headings and the rich text for blocks of text.
To access the content from a rich text field in a document, fetch the document from the API, like in the following example:
import { createClient } from '@/prismicio'
export default async function Page({ params }) {
const client = createClient()
const document = await client.getByUID('post', 'my-first-post')
// Add return statement
}
<script>
export default {
name: 'App',
data() {
return {
response: null
}
},
methods: {
async getContent() {
const response = await this.$prismic.api.getByUID('post', 'my-first-post')
this.response = response
}
},
created() {
this.getContent()
}
}
</script>
// Add template tags
<script setup>
const { client } = usePrismic();
const { data: page } = await useAsyncData('page', async () => {
const document = await client.getByUID('post', 'my-first-post')
if (document) {
return document;
}
});
</script>
<script>
export default {
name: 'App',
data() {
return {
response: null
}
},
methods: {
async getContent() {
const response = await this.$prismic.api.getByUID('post', 'my-first-post')
this.response = response
}
},
created() {
this.getContent()
}
}
</script>
// Add template tags
// Update the path to your prismic.js file
import { client } from '../prismic'
function Page() {
const document = client.getByUID('post', 'my-first-post');
// Add return statement
}
<script>
// Update the path to your prismicio.js file
import createClient from './../prismicio'
import * as prismic from '@prismicio/client'
const client = createClient()
const document = await client.getByUID('post', 'my-first-post')
</script>
import path from 'path'
import express from 'express'
import { fileURLToPath } from 'url'
import { client } from './config/prismicConfig.js'
import * as prismic from '@prismicio/client'
const app = express();
const port = process.env.PORT || 3000
app.set('view engine', 'ejs')
const __dirname = path.dirname(fileURLToPath(import.meta.url))
app.use(express.static(path.join(__dirname, 'views')))
app.use(express.json());
app.use((req, res, next) => {
res.locals.ctx = {
prismic,
}
next()
})
app.get('/', async (req, res) => {
const document = await client.getByUID('post', 'my-first-post')
res.render('page', { document })
})
app.listen(port, () => {})
// Update the path to your prismic.js file
import client from '../prismic.js'
const init = async () => {
const prismicDoc = await client.getByUID('post', 'my-first-post')
}
init()
This line fetches a document based on its unique identifier — or uid.
You can filter API results by rich text using three filters:
fulltext
to search for documents that contain a given stringhas
to search for documents where a rich text field is populated with contentmissing
to search for documents where a rich text field is not populated with content
Note: has
and missing
don't work with fields in Slices or groups.
Here’s a fulltext
query:
import { createClient } from '@/prismicio'
export default async function Page({ params }) {
const client = createClient()
const document = await client.getByUID('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
})
// Add return statement
}
<script>
export default {
name: 'App',
data() {
return {
response: null
}
},
methods: {
async getContent() {
const response = await this.$prismic.api.getByUID('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
}
this.response = response
},
created() {
this.getContent()
}
}
</script>
// Add template tags
<script setup>
const { client } = usePrismic();
const { data: post } = await useAsyncData('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
if (document) {
return document;
}
});
</script>
<script>
export default {
name: 'App',
data() {
return {
response: null
}
},
methods: {
async getContent() {
const response = this.$prismic.api.getByUID('post', 'my-first-post', {
filters: [prismic.filter.fulltext('document', 'Hello')],
})
this.response = response
}
},
created() {
this.getContent()
}
}
</script>
// Add template tags
// Update the path to your prismic.js file
import { client } from '../prismic'
function Page() {
const document = await client.getByUID('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
})
// Add return statement
}
<script>
// Update the path to your prismicio.js file
import createClient from './../prismicio'
import * as prismic from '@prismicio/client'
const client = createClient()
const document = await client.getByUID('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
})
</script>
import path from 'path'
import express from 'express'
import { fileURLToPath } from 'url'
import { client } from './config/prismicConfig.js'
import * as prismic from '@prismicio/client'
const app = express();
const port = process.env.PORT || 3000
app.set('view engine', 'ejs')
const __dirname = path.dirname(fileURLToPath(import.meta.url))
app.use(express.static(path.join(__dirname, 'views')))
app.use(express.json());
app.use((req, res, next) => {
res.locals.ctx = {
prismic,
}
next()
})
app.get('/', async (req, res) => {
const document = await client.getByUID('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
})
res.render('page', { document })
})
app.listen(port, () => {})
// Update the path to your prismic.js file
import client from '../prismic.js'
const init = async () => {
const prismicDoc = await client.getByUID('page', uid, {
filters: [prismic.filter.fulltext('document', 'Hello')],
})
}
init()
// API response example of a rich text field
{
//...
"example_rich_text": {
"type": "StructuredText",
"config": {
"single": "paragraph",
"label": "Rich Content",
"placeholder": "Rich Content",
"labels": [
"right-align",
"center-align"
]
}
}
}
When you fetch content from the Prismic API, you cannot immediately inject it into your web app. First, you must process it with one of Prismic's open-source packages. Our packages include functions and components to turn rich text JSON into HTML.
import { PrismicRichText } from '@prismicio/react'
export default function TextSlice({ slice }) {
return (
<PrismicRichText field={slice.primary.text} />
)
}
import { getSliceComponentProps } from '@prismicio/vue/components'
<script>
export default {
name: 'TextSlice',
props: {
slice: Object
}
}
</script>
<template>
<prismic-rich-text :field='slice.primary.text' />
</template>
<script setup>
</script>
<template>
<prismic-rich-text :field='slice.primary.text' />
</template>
<script>
export default {
name: 'TextSlice',
props: {
slice: Object
}
}
</script>
<template>
<prismic-rich-text :field='slice.primary.text' />
</template>
import { PrismicRichText } from '@prismicio/react'
export default function TextSlice({ slice }) {
return (
<PrismicRichText field={slice.primary.text} />
)
}
<script>
import * as prismic from '@prismicio/client'
export let slice
</script>
{@html prismic.asHTML(slice.primary.text)}
<%- ctx.prismic.asHTML(document.data.text, { serializer }) %>
// Update the path to your prismic.js file
import client from '../prismic.js'
import * as prismic from '@prismicio/client'
const init = async () => {
const prismicDoc = await client.getByUID('post', 'my-first-post')
const contentText = prismic.asHTML(prismicDoc.data.text, { serializer });
}
init()
Prismic offers two features for advanced rich text customization: custom labels and the rich text serializer.
For special formatting in your rich text or title fields, you can add custom formatting by using labels. For example, by default, the Prismic kits will render a custom label as a <span>
with a class of the label's name. (This is an advanced configuration that can only be set up in the JSON editor.) Here is an example of a rich text field with the label options "right-align"
and "center-align"
:
{
"example_rich_text": {
"type": "StructuredText",
"config": {
"single": "paragraph",
"label": "Rich Content",
"placeholder": "Rich Content",
"labels": ["right-align", "center-align"]
}
}
}
A rich text serializer determines how each element in a rich text field should be rendered. For example, if you're rendering HTML, you might want a heading1 element to be wrapped in <h1>
tags.
Prismic's development package, @prismicio/client
, converts rich text content into HTML by default, so it’s not necessary to provide your own rich text serializer. However, you might want to change how rich text is serialized. Maybe you want to add a very precise set of class names to certain elements or to add a unique HTML tag based on the label. In that case, you can provide your own rich text serializer to alter the serialization selectively.
Serializing in Vue 2 and Nuxt 2
Prismic's integrations for Vue 2 and Nuxt 2 rely on an older package, prismic-dom
, which has a different implementation of rich text serializing. For more information, see the prismic-dom
Technical Reference.
Each key on the rich text serializer object is the name of a rich text element (such as paragraph
, label
, or heading1
) and the value should be a function that returns a converted value.
Here's a brief example. This code says, "if the type of the element is 'label,' return the text inside strong
tags with a class of the label."
import * as prismic from '@prismicio/client'
import { PrismicRichText } from '@prismicio/react'
/*
In the PrismicRichText component from @prismicio/react,
the rich text serializer is handled by the `components`
prop.
*/
const components = {
label: ({ node, children }) => (
<strong className={node.data.label}>{children}</strong>
),
}
const Text = ({ slice }) => {
return (
<section>
<PrismicRichText field={slice.primary.text} components={components} />
</section>
)
}
export default Text
Nuxt 2 relies on an older SDK, with a different implementation
of rich text serializing. For more information, see the
technical reference for prismic-dom:
https://prismic.io/docs/prismic-dom-technical-reference
<script setup>
const richTextSerializer = {
label: ({ node, children }) =>
`<strong className={node.data.label}>{children}</strong>`,
};
</script>
<template>
<section>
<prismic-rich-text
:field='slice.primary.text'
:serializer='richTextSerializer'
/>
</section>
</template>
Vue 2 relies on an older SDK, with a different implementation
of rich text serializing. For more information, see the
technical reference for prismic-dom:
https://prismic.io/docs/prismic-dom-technical-reference
import * as prismic from '@prismicio/client'
import { PrismicRichText } from '@prismicio/react'
/*
In the PrismicRichText component from @prismicio/react,
the rich text serializer is handled by the `components`
prop.
*/
const components = {
label: ({ node, children }) => (
<strong className={node.data.label}>{children}</strong>
),
}
const Text = ({ slice }) => {
return (
<section>
<PrismicRichText field={slice.primary.text} components={components} />
</section>
)
}
export default Text
<script>
import * as prismic from '@prismicio/client'
const serializer = {
label: ({children, key, type, node, text}) => (
`<strong class="${node.data.label}">${children}</strong>`
)
}
export let slice
</script>
{@html prismic.asHTML(slice.primary.text, { serializer })}
<%
const serializer = {
label: ({children, key, type, node, text}) => (
`<strong class="${node.data.label}">${children}</strong>`
)
}
%>
<%- ctx.prismic.asHTML(document.data.text, { serializer }) %>
import * as prismic from '@prismicio/client'
const serializer = {
paragraph: ({children}) => (
return `<blockquote>${children}</blockquote>`
}
return null
}
const contentText = prismic.asHTML(prismicDoc.data.text, { serializer });
The methods on the components object receive an params object with four parameters: children
, key
, type
, node
, text
.
children
array
An array of all child elements, which have already been serialized. Example:
[ "Massa sapien faucibus ", "<strong>cras tincidunt lobortis</strong>", ". Pharetra pharetra massa ", "<span class=\"codespan\">ultricies</span>", " morbi tincidunt augue interdum." ]
key
string
A unique key for React's key prop.
node
object
A description of the entire element. For inline elements, this includes the start index, end index, type, and — if the element is a custom label — the name of the label. Example:
{ "start": 0, "end": 6, "type": "label", "data": { "label": "codespan" } }
For block-level elements (eg: paragraph, headings, pre), this includes the type, the text content, and an array containing all of the inline elements. Example:
{ type: 'paragraph', text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', spans: [ { start: 63, end: 71, type: 'label', data: { label: 'codespan' } }, ] }
text
string
The content of the element. Example: "Lorem ipsum"
type
string
The type of the element.Example: "span"
, "heading2"
, "paragraph"
, "image"
.
To see how @prismicio/client
can serialize every available element, view the example in the @prismicio/richtext Technical Reference or this simplified version of a rich text serializer handling all cases:
const linkResolver = (doc) => '/' + doc.uid
const serializer = {
heading1: ({ children }) => `<h1>${children}</h1>`,
heading2: ({ children }) => `<h2>${children}</h2>`,
heading3: ({ children }) => `<h3>${children}</h3>`,
heading4: ({ children }) => `<h4>${children}</h4>`,
heading5: ({ children }) => `<h5>${children}</h5>`,
heading6: ({ children }) => `<h6>${children}</h6>`,
paragraph: ({ children }) => `<p>${children}</p>`,
preformatted: ({ node }) => `<pre>${JSON.stringify(node.text)}</pre>`,
strong: ({ children }) => `<strong>${children}</strong>`,
em: ({ children }) => `<em>${children}</em>`,
listItem: ({ children }) => `<li>${children}</li>`,
oListItem: ({ children }) => `<li>${children}</li>`,
list: ({ children }) => `<ul>${children}</ul>`,
oList: ({ children }) => `<ol>${children}</ol>`,
image: ({ node }) => {
const linkUrl = node.linkTo ? linkResolver(node.linkTo) : null
const linkTarget =
node.linkTo && node.linkTo.target
? `target="${node.linkTo.target}" rel="noopener"`
: ''
const wrapperClassList = [node.label || '', 'block-img']
const img = `<img src="${node.url}" alt="${
node.alt ? node.alt : ''
}" copyright="${node.copyright ? node.copyright : ''}" />`
return `
<p class="${wrapperClassList.join(' ')}">
${linkUrl ? `<a ${linkTarget} href="${linkUrl}">${img}</a>` : img}
</p>
`
},
embed: ({ node }) => `
<div data-oembed="${node.oembed.embed_url}"
data-oembed-type="${node.oembed.type}"
data-oembed-provider="${node.oembed.provider_name}"
${label(node)}>
${node.oembed.html}
</div>
`,
hyperlink: ({ node, children }) => {
const target = node.data.target
? `target="${node.data.target}" rel="noopener"`
: ''
const url = linkResolver(node.data)
return `<a ${target} href="${url}">${children}</a>`
},
label: ({ node, children }) => {
return `<span class="${node.data.label}">${children}</span>`
},
span: ({ text }) => (text ? text : ''),
}
Was this article helpful?
Can't find what you're looking for? Spot an error in the documentation? Get in touch with us on our Community Forum or using the feedback form above.