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 format

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
HTML
Copy
"blog_post_content": "<div>I started reading <em>Great Expectations</em>!</div>"
Markdown
Copy
"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:

Copy
"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.

Rich text fields

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:

Copy
{
  "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.

Query rich text

To access the content from a rich text field in a document, fetch the document from the API, like in the following example:

Next.js
app/[uid]/page.js
Next.js
Copy
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
}
Nuxt 2
pages/_uid.vue
Nuxt 2
Copy
<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
Nuxt 3
pages/[uid].vue
Nuxt 3
Copy
<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>
Vue.js
pages/_uid.vue
Vue.js
Copy
<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
React.js
pages/[uid].js
React.js
Copy
// 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
}
SvelteKit
src/routes/[uid]/+page.server.js
SvelteKit
Copy
<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>
Express
index.js
Express
Copy
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, () => {})
Vanilla JavaScript
post.js
Vanilla JavaScript
Copy
// 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 string
  • has to search for documents where a rich text field is populated with content
  • missing 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:

Next.js
app/[uid]/page.js
Next.js
Copy
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
}
Nuxt 2
pages/_uid.vue
Nuxt 2
Copy
<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
Nuxt 3
pages/[uid].vue
Nuxt 3
Copy
<script setup>
const { client } = usePrismic();

const { data: post } = await useAsyncData('page', uid, {
  filters: [prismic.filter.fulltext('document', 'Hello')],

  if (document) {
    return document;
  }
});
</script>
Vue.js
pages/_uid.vue
Vue.js
Copy
<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
React.js
pages/[uid].js
React.js
Copy
// 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
}
SvelteKit
src/routes/[uid]/+page.server.js
SvelteKit
Copy
<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>
Express
views/pages/[uid].js
Express
Copy
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, () => {})
Vanilla JavaScript
post.js
Vanilla JavaScript
Copy
// 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()

Render rich text

Here's what rich text from the Prismic API looks like:

Copy
// 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.

Next.js
slices/Text/index.js
Next.js
Copy
import { PrismicRichText } from '@prismicio/react'

export default function TextSlice({ slice }) {
  return (
    <PrismicRichText field={slice.primary.text} />
  )
}
Nuxt 2
slices/Text/index.vue
Nuxt 2
Copy
import { getSliceComponentProps } from '@prismicio/vue/components'

<script>
export default {
  name: 'TextSlice',
  props: {
    slice: Object
  }
}
</script>

<template>
  <prismic-rich-text :field='slice.primary.text' />
</template>
Nuxt 3
slices/Text/index.vue
Nuxt 3
Copy
<script setup>
</script>

<template>
  <prismic-rich-text :field='slice.primary.text' />
</template>
Vue.js
slices/Text/index.vue
Vue.js
Copy
<script>
export default {
  name: 'TextSlice',
  props: {
    slice: Object
  }
}
</script>

<template>
  <prismic-rich-text :field='slice.primary.text' />
</template>
React.js
slices/Text/index.js
React.js
Copy
import { PrismicRichText } from '@prismicio/react'

export default function TextSlice({ slice }) {
  return (
    <PrismicRichText field={slice.primary.text} />
  )
}
SvelteKit
src/lib/slices/Text/index.svelte
SvelteKit
Copy
<script>
  import * as prismic from '@prismicio/client'
  export let slice
</script>

{@html prismic.asHTML(slice.primary.text)}
Express
views/pages/home.ejs
Express
Copy
<%- ctx.prismic.asHTML(document.data.text, { serializer }) %>
Vanilla JavaScript
post.js
Vanilla JavaScript
Copy
// 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()

Customize rich text

Prismic offers two features for advanced rich text customization: custom labels and the rich text serializer.

Rich text labels

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":

Copy
{
  "example_rich_text": {
    "type": "StructuredText",
    "config": {
      "single": "paragraph",
      "label": "Rich Content",
      "placeholder": "Rich Content",
      "labels": ["right-align", "center-align"]
    }
  }
}

Rich text serializer

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

Next.js
slices/Text/index.js
Next.js
Copy
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
Nuxt 2
Copy
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
Nuxt 3
slices/Text/index.vue
Nuxt 3
Copy
<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.js
Vue.js
Copy
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
React.js
slices/Text/index.js
React.js
Copy
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
SvelteKit
src/lib/slices/Text/index.svelte
SvelteKit
Copy
<!-- Strong.svelte -->

<script>
  export let node
</script>

<strong class={node.data.label}>
  <slot />
</strong>

<!-- RichText.svelte -->
<script>
  import { PrismicRichText } from '@prismicio/svelte'

  const components = {
    strong: Strong
  }

  export let slice
</script>

<PrismicRichText field={slice.primary.text} {components} />
Express
views/pages/home.ejs
Express
Copy
<%
  const serializer = {
    label: ({children, key, type, node, text}) => (
      `<strong class="${node.data.label}">${children}</strong>`
    )
  }
%>

<%- ctx.prismic.asHTML(document.data.text, { serializer }) %>
Vanilla JavaScript
post.js
Vanilla JavaScript
Copy
import * as prismic from '@prismicio/client'

const serializer = {
  paragraph: ({children}) => (
    return `<strong class="${node.data.label}">${children}</strong>`
  }
  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:

Copy
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?
Not really
Yes, Thanks

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.