HTML Serializer

On this article you'll learn what an HTML 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. An HTML Serializer defines how Rich Text content is converted and rendered on your website.

In Prismic, Rich Text is stored in an array. An HTML Serializer is a function that defines how the elements of that array should be joined.

Prismic's development package, @prismicio/helpers, convert Rich Text content into HTML by default, so it’s not necessary to provide your own HTML Serializer. However, you might want to change how Rich Text is serialized into HTML. In that case, you can provide your own HTML Serializer to alter the serialization selectively.

There are two types of HTML Serializer:

  • An object HTML Serializer
  • A functional HTML Serializer

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

Object HTML Serializer

An object HTML Serializer is an object that uses methods to describe how Prismic's Rich Text elements should be rendered.

Each key on the HTML 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 prismicH from '@prismicio/helpers'
import { PrismicRichText } from '@prismicio/react'

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

const Text = ({ slice }) => {
  return (
    <section>
      {prismicH.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 prismicH from "@prismicio/helpers";
import { PrismicRichText } from "@prismicio/react";

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

const Text = ({ slice }) => {
  return (
    <section>
      {prismicH.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 prismicH from '@prismicio/helpers'
import { PrismicRichText } from '@prismicio/react'

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

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

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

  The Prismic integration for Nuxt doesn't not yet support 
  an object HTML 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 prismicH from "@prismicio/helpers"

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

  export let slice
</script>

{@html prismicH.asHTML(slice.primary.text, null, htmlSerializer)}
Vanilla JavaScript
Vanilla JavaScript
Copy
import * as prismicH from "@prismicio/helpers"  
  
const htmlSerializer = {  
  label: ({children, key, type, node, text}) => (  
    `<strong class="${node.data.label}">${children}</strong>`
  )  
}  
  
// `document` is a Document from the Prismic API  
prismicH.asHTML(document.data.example_rich_text_field, null, htmlSerializer)

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 HTML Serializer

An HTML 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 prismicH from '@prismicio/helpers'

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

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

export default Text
Gatsby
src/slices/Text/index.js
Gatsby
Copy
import { graphql } from "gatsby";
import * as prismicH from "@prismicio/helpers";
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>
      {prismicH.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 prismicH from '@prismicio/helpers'

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

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

export default Text
Nuxt.js
slices/Text/index.vue
Nuxt.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>
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 prismicH from "@prismicio/helpers"

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

{@html prismicH.asHTML(slice.primary.text, null, htmlSerializer)}
Vanilla JavaScript
text.js
Vanilla JavaScript
Copy
import * as prismicH from "@prismicio/helpers"

function htmlSerializer(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
prismicH.asHTML(document.data.example_rich_text_field, null, htmlSerializer)

Accessible arguments

The functional HTML 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/helpers v2 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 an HTML Serializer

Sometimes the default HTML 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 HTML Serializer comes in handy.

Use an HTML Serializer with your framework

To learn more about how to use your HTML Serializer in your project, see the documentation for your chosen framework.

See all HTML Serializer elements

To see how @prismicio/helpers can serialize every available element, view the example in the @prismicio/richtext Technical Reference or this simplified version of a functional HTML Serializer handling all cases:

  • Object
  • Functional
Object
Copy
const linkResolver = (doc) => '/' + doc.uid

const htmlSerializer = {
  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 htmlSerializer(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.