Rich Text Serializer

On this article you'll learn what a rich text serializer is, and when you can use it in your project.


In Prismic, rich text lets content writers format text with headings, bold styling, and more. Content is stored in a Prismic-specific format designed to convert to other formats, such as HTML. A rich text serializer defines how rich text content is converted and rendered on your website.

In Prismic, rich text is stored in an array. A rich text serializer is a function that defines how the elements of that array should be joined.

Prismic's development package, @prismicio/client, convert 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 into HTML. In that case, you can provide your own rich text serializer to alter the serialization selectively.

There are two types of rich text serializer:

  • An object rich text serializer
  • A functional rich text serializer

You can use an object rich text serializer in implementations that depend on @prismicio/client, which includes all of our officially supported framework integrations except Vue.js and Nuxt.

Object rich text serializer

An object rich text serializer is an object that uses methods to describe how Prismic's rich text elements should be rendered.

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.

Example

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'

const components = {
  label: ({ node, children }) => (
    <strong className={node.data.label}>{children}</strong>
  ),
}

const Text = ({ slice }) => {
  return (
    <section>
      {prismic.isFilled.richText(slice.primary.text) && (
        <PrismicRichText field={slice.primary.text} components={components} />
      )}
    </section>
  )
}

export default Text
Gatsby
src/slices/Text/index.js
Gatsby
Copy
import { graphql } from "gatsby";
import * as prismic from "@prismicio/client";
import { PrismicRichText } from "@prismicio/react";

const components = {
  label: ({ node, children }) => (
    <strong className={node.data.label}>{children}</strong>
  ),
}

const Text = ({ slice }) => {
  return (
    <section>
      {prismic.isFilled.richText(slice.primary.text.richText) && (
        <PrismicRichText field={slice.primary.text.richText} components={components} />
      )}
    </section>
  );
};

export default Text;

export const fragment = graphql`
  fragment PrismicText on PrismicText {
    ... on PrismicTextDefault {
      variation
      primary {
        text {
          richText
        }
      }
    }
  }
`;
React.js
slices/Text/index.js
React.js
Copy
import * as prismic from '@prismicio/client'
import { PrismicRichText } from '@prismicio/react'

const components = {
  label: ({ node, children }) => (
    <strong className={node.data.label}>{children}</strong>
  ),
}

const Text = ({ slice }) => {
  return (
    <section>
      {prismic.isFilled.richText(slice.primary.text) && (
        <PrismicRichText field={slice.primary.text} components={components} />
      )}
    </section>
  )
}

export default Text
Nuxt 2
slices/Text/index.vue
Nuxt 2
Copy
<!--

  The Prismic integration for Nuxt doesn't not yet support 
  an object rich text serializer. See the functional serializer 
  documentation below.

-->
Vue.js
src/components/slices/Text/index.vue
Vue.js
Copy
<!--

  The object serializer is only available with Vue 3.
  If you're using Vue 2, use the functional serializer,
  as described below.

-->

<template>
  <prismic-rich-text
    :field="slice.primary.text"
    :html-serializer="htmlSerializer"
  />
</template>

<script>
import { getSliceComponentProps } from "@prismicio/vue";

const htmlSerializer = {
  label: ({children, key, type, node, text}) => (
    `<strong class="${node.data.label}">${children}</strong>`
  )
}

export default {
  props: getSliceComponentProps(),
};
</script>
Svelte
src/lib/slices/Text/index.svelte
Svelte
Copy
<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 })}
Vanilla JavaScript
Vanilla JavaScript
Copy
import * as prismic from '@prismicio/client'  
  
const serializer = {  
  label: ({children, key, type, node, text}) => (  
    `<strong class="${node.data.label}">${children}</strong>`
  )  
}  
  
// `document` is a document from the Prismic API  
prismic.asHTML(document.data.example_rich_text_field, { serializer })

Accessible parameters

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

Functional rich text serializer

A rich text serializer function can be passed to a Prismic rich text helper or component. The function receives a rich text block, such as a paragraph or heading, and returns a converted version of it, such as an HTML representation of it. The function is called for each individual block in the rich text value.

Example

Here's a brief example. This code says, "if the type of the element is 'label,' return the text inside strong tags with a className of the label."

Next.js
slices/Text/index.js
Next.js
Copy
import * as prismic from '@prismicio/client'

const serializer = (type, element, text, children) => {
  if (type === 'label')
    return `<strong className="${element.data.label}">${children}</strong>`
  return null
}

const Text = ({ slice }) => {
  return (
    <section>
      {prismic.isFilled.richText(slice.primary.text) && (
        <PrismicRichText field={slice.primary.text.richText} htmlSerializer={serializer} />
      )}
    </section>
  )
}

export default Text
Gatsby
src/slices/Text/index.js
Gatsby
Copy
import { graphql } from "gatsby";
import * as prismic from "@prismicio/client";
import { PrismicRichText } from "@prismicio/react";

const htmlSerializer = (type, element, text, children) => {
  if (type === 'label')
    return `<strong className="${element.data.label}">${children}</strong>`
  return null
}

const Text = ({ slice }) => {
  return (
    <section>
      {prismic.isFilled.richText(slice.primary.text.richText) && (
        <PrismicRichText field={slice.primary.text.richText} htmlSerializer={htmlSerializer} />
      )}
    </section>
  );
};

export default Text;

export const fragment = graphql`
  fragment PrismicText on PrismicText {
    ... on PrismicTextDefault {
      variation
      primary {
        text {
          richText
        }
      }
    }
  }
`;
React.js
slices/Text/index.js
React.js
Copy
import * as prismic from '@prismicio/client'

const serializer = (type, element, text, children) => {
  if (type === 'label')
    return `<strong className="${element.data.label}">${children}</strong>`
  return null
}

const Text = ({ slice }) => {
  return (
    <section>
      {prismic.isFilled.richText(slice.primary.text) && (
        <PrismicRichText field={slice.primary.text.richText} htmlSerializer={serializer} />
      )}
    </section>
  )
}

export default Text
Nuxt 2
slices/Text/index.vue
Nuxt 2
Copy
<template>
  <prismic-rich-text
    :field="slice.primary.text"
    :html-serializer="htmlSerializer"
  />
</template>

<script>
export default {
  name: 'Text',
  props: {
    slice: Object,
  },
  methods: {
    htmlSerializer: function (type, element, text, children) {
      if (type === 'label')
        return `<strong className="${element.data.label}">${children}</strong>`
      return null
    },
  },
}
</script>
Vue.js
src/components/slices/Text/index.vue
Vue.js
Copy
<template>
  <prismic-rich-text
    :field="slice.primary.text"
    :html-serializer="htmlSerializer"
  />
</template>

<script>
export default {
  name: 'Text',
  props: {
    slice: Object,
  },
  methods: {
    htmlSerializer: function (type, element, text, children) {
      if (type === 'label')
        return `<strong className="${element.data.label}">${children}</strong>`
      return null
    },
  },
}
</script>
Svelte
src/lib/slices/Text/index.svelte
Svelte
Copy
<script>
  import * as prismic from '@prismicio/client'

  export let slice
  
  function serializer(type, element, text, children) {
    if (type === 'label') {
      return `<strong class="${element.data.label}">${children}</strong>`
    }
    return null
  }
</script>

{@html prismic.asHTML(slice.primary.text, { serializer })}
Vanilla JavaScript
text.js
Vanilla JavaScript
Copy
import * as prismic from '@prismicio/client'

function serializer(type, element, text, children) {
  if (type === 'label') {
    return `<strong class="${element.data.label}">${children}</strong>`
  }
  return null
}

// `document` is a document from the Prismic API
prismic.asHTML(document.data.example_rich_text_field, { serializer })

Accessible arguments

The functional rich text serializer receives four parameters in the following order: type, element, content, children.

type

string

The type of the element. Example: "span", "heading2", "paragraph", "image".

element

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 text of the element. Example:

"Lorem ipsum"

children

array of strings

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

Note that @prismicio/client v7 joins this array as a string by default, so the children parameter will look like this:

"Massa sapien faucibus, <strong>cras tincidunt lobortis</strong>. Pharetra pharetra massa <span  class=\"codespan\">ultricies</span> morbi tincidunt augue interdum."

When to use a rich text serializer

Sometimes the default rich text serializer doesn't cover your use case. 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. That's when the rich text serializer comes in handy.

Use a rich text serializer with your framework

To learn more about how to use your rich text serializer in your project, see the documentation for your chosen framework.

See all rich text serializer elements

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 functional rich text serializer handling all cases:

  • Object
  • Functional
Object
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 : ''),
}
Functional
Copy
function serializer(type, element, content, children) {
  const linkResolver = (doc) => '/' + doc.uid
  switch (type) {
    case 'heading1':
      return `<h1>${children}</h1>`
    case 'heading2':
      return `<h2>${children}</h2>`
    case 'heading3':
      return `<h3>${children}</h3>`
    case 'heading4':
      return `<h4>${children}</h4>`
    case 'heading5':
      return `<h5>${children}</h5>`
    case 'heading6':
      return `<h6>${children}</h6>`
    case 'paragraph':
      return `<p>${children}</p>`
    case 'preformatted':
      return `<pre>${JSON.stringify(element.text)}</pre>`
    case 'strong':
      return `<strong>${children}</strong>`
    case 'em':
      return `<em>${children}</em>`
    case 'listItem':
      return `<li>${children}</li>`
    case 'oListItem':
      return `<li>${children}</li>`
    case 'list':
      return `<ul>${children}</ul>`
    case 'oList':
      return `<ol>${children}</ol>`
    case 'image':
      const linkUrl = element.linkTo ? linkResolver(element.linkTo) : null
      const linkTarget =
        element.linkTo && element.linkTo.target
          ? `target="${element.linkTo.target}" rel="noopener"`
          : ''
      const wrapperClassList = [element.label || '', 'block-img']
      const img = `<img src="${element.url}" alt="${
        element.alt ? element.alt : ''
      }" copyright="${element.copyright ? element.copyright : ''}" />`

      return `
        <p class="${wrapperClassList.join(' ')}">
          ${linkUrl ? `<a ${linkTarget} href="${linkUrl}">${img}</a>` : img}
        </p>
      `
    case 'embed':
      return `
        <div data-oembed="${element.oembed.embed_url}"
          data-oembed-type="${element.oembed.type}"
          data-oembed-provider="${element.oembed.provider_name}"
          ${label(element)}>
          ${element.oembed.html}
        </div>
      `
    case 'hyperlink':
      const target = element.data.target
        ? `target="${element.data.target}" rel="noopener"`
        : ''
      const url = linkResolver(element.data)
      return `<a ${target} href="${url}">${children}</a>`
    case 'label':
      return `<span class="${element.data.label}">${children}</span>`
    case 'span':
      return content ? content : ''
    default:
      return ''
  }
}

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.