New release: Conditional Fields
May 26, 2021

3 things you need to know when getting started with Portable Text

By Kapehe

When using Sanity, it's important to understand Portable Text and what it is doing for your content. In this guide, we'll go over the 3 things to understand when getting started with Portable Text.

We have started a channel for beginners in the Sanity Slack Community, #getting-started. This channel is meant for those coming into the Sanity Community to feel safe to ask beginner Sanity questions or find past questions that others came across. We want to make sure that everyone feels supported, no matter what stage you are at with Sanity. Recently, we got a good list of questions in that channel about Portable Text.

I'll be honest, Portable Text didn't come easy to me, since it represents a new and different way to approach rich text. But I will say, I think I've got a good hold on it. So I'm going to take you with me on my Portable Text journey and share with you what I found are 3 important things to know when getting started with Portable Text:

  • blocks
  • marks
  • serializers

What is Portable Text?

Before you jump into the 3 things to know when getting started with Portable Text, let's cover what Portable Text is! You may be familiar with using HTML or Markdown when using another CMS. At Sanity, we use Portable Text. So when you query for your content, your rich text content is returned as Portable Text.

When you restrict your content to HTML or Markdown only, you restrict it from the numerous platforms or types of software where you can send it. When using Portable Text, you can reuse your content across the web. It was built to be used across any format or platform. Need it to go to a browser with Vue.js? Or to a mobile app with React Native? Or spoken by a voice assistant? It can be written once in the Sanity Studio, and reused across different platforms, frameworks, anywhere you need your content to go!

What are “blocks”?

Portable Text is broken into blocks of content, an array of objects, to be more exact. Imagine a blog post with a paragraph, an image, a sub-heading, another paragraph, and a YouTube embed. That would be considered 5 blocks of content. Portable Text is a JSON-based rich text specification. And we can see an example of that JSON format below. This format is meant to be easily readable for computers, reusable, and queriable. People interact with it by working in the Portable Text editor for Sanity Studio.

Here's a small example of three blocks of content. Notice the array within the "body" is separated into three blocks; as an array of objects with some specific and recurring keys:

{
  "_id": "drafts.6ae3ac6f-f033-403f-ad7a-65920daf8a6f",
  "_rev": "66f32k-seu-0dv-rmo-44vyj9oxq",
  "_type": "post",
  "_updatedAt": "2021-05-20T20:02:29.542Z",
  "body": [
    {
      "_type": "block",
      "_key": "e5f8351bb4dd",
      "style": "normal",
      "markDefs": [],
      "children": [
        {
          "_type": "span",
          "_key": "d06d05d1ed5e",
          "text": "This is block #1.",
          "marks": []
        }
      ]
    },
    {
      "_type": "block",
      "_key": "cf12b1566c13",
      "style": "normal",
      "markDefs": [],
      "children": [
        {
          "_type": "span",
          "_key": "feb044998e9c",
          "text": "Block #2 is after that.",
          "marks": []
        }
      ]
    },
    {
      "_type": "block",
      "_key": "643bf733b859",
      "style": "normal",
      "markDefs": [],
      "children": [
        {
          "_type": "span",
          "_key": "6b8cc1d52738",
          "text": "But block #3 is the end of our content.",
          "marks": []
        }
      ]
    }
  ]
}

As you can see, all of these objects have the "_type": "block". If we had custom blocks, like a YouTube embed, it would probably say "_type": "youTube".

What are marks in Portable Text?

A mark in Portable Text is how we mark up inline text in the editor within the Sanity Studio. This can include marking text as strong or emphazised, as a code string, highlighted, or to link to an internal/external resource.

In the code block below, you see how Portable Text looks in its JSON format. Let's work through each line!

Note: This is different than the code block above. That code block had three blocks, this is one block, broken down even further.

{
  "_key": "9d2d1ed68d84",
  "_type": "block", // the schema type for the Portable Text
  "children": [
    {
      "_type": "span",
      "marks": [],
      "text": "I am "
    },
    {
      "_type": "span",
      "marks": [
        "strong"
      ],
      "text": "strong and "
    },
    {
      "_type": "span",
      "marks": [
        "strong",
        "cbe9d12c6af9" // notice this matches the "_key" below.
      ],
      "text": "annotated"
    },
    {
      "_type": "span",
      "marks": [],
      "text": "."
    }
  ],
  "markDefs": [
    {
      "_key": "cbe9d12c6af9", // notice this matches the "marks" above.
      "_type": "link",
      "href": "https://www.sanity.io/"
    }
  ],
  "style": "normal"
}

What does it all mean?

  • "children" - the sections within our Portable Text block.
  • "_type" - We'll see two layers of _type. The first one: "_type": "block" is stating that this a text paragraph. There may be many different blocks of content in your project. It's typically this type of block that has "children". The objects in the "children" array are where our text and marks live. This "_type" is called a "span".
  • "marks" - There is where we add some metadata about a span of text. We'll look at the second "span". This one has "strong" meaning that whatever text is within this "span", it will typically be presented with a bold typeface.
  • "text" - This is the text. The content is broken up into spans that you can customize further. If you look at the second "span", that "strong" in the "marks" array is going to bolden the text string: "strong and ".
  • Looking at the other "spans", you see "strong" and "cbe9d12c6af9" on the third one. That means the text "annotated" will be bold and linked. The seemingly arbitrary combination of letters and numbers is a reference to an object that you'll find within markDefs
  • "markDefs" - You'll find the string "cbe9d12c6af9" in the third span and within the "markDefs" array. In other words, you can mark text with more complex data, expressed as an object with keys and values. It can be whatever you want, but here it's used to express a link. It has a field called "href" that's used for storing a URL.

Now how does this all look in the end? That's up to you! You can “serialize” these blocks of content into a more readable format for putting it on the web, an app, or somewhere else (more on "serializers" below). If you serialize it into HTML, this JSON of content will look like this: I am strong and annotated

Example

This code block and more information on "span" can be found here.

Marks can be either a decorator or annotation. Let's look at what each one means and what the difference is between them.

What is a decorator?

Now that you have broken down the block of Portable Text and what the numerous "marks" mean. Let's take that a little further. What if you want to customize the rich text editor and add different ways of marking up text?

Let's say we want to add the following decorators:

  • strong
  • emphasis
  • code
  • highlighting

That code would look like this:

export default {
  name: 'content',
  title: 'Content',
  type: 'array',
  of: [
    {
      type: 'block',
      marks: {
        decorators: [
          { title: 'Strong', value: 'strong' },
          { title: 'Emphasis', value: 'em' },
          { title: 'Code', value: 'code' },
          { title: 'Highlight', value: 'highlight' }
        ]
      }
    }
  ]
}

This code would look like this in the rich text editor in the Sanity Studio:

As you are typing into the rich text editor, you can highlight certain words, click on one of these in the menu, and add that styling. Say you added strong to some text that says, "I am bold". Once that styling is added to your text within your rich text editor, you can view your JSON version of your Portable Text and see that it would look like this:

{
  "_type": "span",
  "marks": [
    "strong"
  ],
  "text": "I am bold"
}

You can customize this however you need to within your rich text editor. Learn more about customizing the editor here.

What is an annotation?

An annotation within Portable Text is when you add an object of keys and values to your text. You are marking your text with a data structure. An example of this would be when you add a URL to the text. Let's look at how that code would look:

"body": [
  {
    "_type": "block",
    "_key": "7de5c159e44d",
    "style": "normal",
    "markDefs": [
      {
        "_type": "link",
        "_key": "8bd7459d6d4d",
        "href": "https://www.sanity.io/"
      }
    ],
    "children": [
      {
        "_type": "span",
        "_key": "c4323f8f316d",
        "text": "Make this clickable to ",
        "marks": []
      },
      {
        "_type": "span",
        "_key": "46299a39782f",
        "text": "Sanity.io",
        "marks": [
          "8bd7459d6d4d"
        ]
      }
    ]

In the toolbar, you can see the different marks you can apply to your text. To add the annotation, we would highlight the text we wanted to have the external URL. That would turn into this:

Some other examples of what an annotation could be:

  • Internal linking using references, like linking to person documents within your Content Lake (or anything else).
  • An array of comments. You highlight a section of the text, an input field for text shows up, the text is entered, and when "Enter" is hit, the author is referenced to this comment and the document you are in.
  • A footnote in the text, like a section of text, has a footnote of information at the bottom of the page where you can learn more about that text.

So what is the difference between annotations and decorators?

Marks are extra information that can be applied to your text. Ultimately, marks can be broken into two sections:

  • Decorator: when that extra information can be expressed as a simple text string. Typically when you want to make it strong, emphazised, or highlighted in another way.
  • Annotation: when the extra information is more complex and can be described as an object with keys and values. For example, a link with the option of opening in a new tab/window, or a footnote that contains its own rich text field.

In the below code, "strong" is the decorator and "<markDefId>" is the annotation of it.

"marks": []

// or

"marks": [
	"strong",
  "<markDefId>"
]

To learn more about these two, decorator and annotation, you can visit this docs page on customization.

What is a serializer?

A serializer is when the blocks of content from the JSON array are stacked together to make a readable format for where we want to present it. Think of it as a car factory where a bunch of parts is brought in, it goes through a machine that puts the parts together, and sends out a completed, fully assembled car with all the parts assembled correctly.

In our example above, our output was "I am strong and annotated." This is because the four spans went through a serializer.

Default serializers can be found for multiple formats. these libraries come with some default serializers that will translate some common data structures to something that renders:

Let's look at the React method. Take a look at this code and we'll walk through it together:

import BlockContent from "@sanity/block-content-to-react";

// ...React component information

<BlockContent
	blocks={post.body}
	serializers={serializers}
  projectId={"yourProjectId"}
  dataset={"yourDataset"}
/>

This is how it would look inside of a React component.

Here are the steps to make this work!

First, we will need to install the package: @sanity/block-content-to-react using the command:

npm install @sanity/block-content-to-react

Next, at the top of the file, we need to import BlockContent :

import BlockContent from "@sanity/block-content-to-react";

Within the return of the component, we'll insert the <BlockContent/> code like this:

<BlockContent
	blocks={post.body}
	serializers={serializers}
  projectId={"yourProjectId"}
  dataset={"yourDataset"}
/>

This will take our post.body content that we query from our Sanity Studio using GROQ or GraphQL. When our frontend grabs this part of the code, it will grab the blocks of Portable Text, serialize it, and show it in a readable format.

Building a custom serializer

In the above example, we are using a default serializer that we got from @sanity/block-content-to-react. Sometimes we don't want the default serializer and we would want to customize things further. Maybe you want to add some custom CSS styling to your links.

We need to convert our Portable Text into a readable format and with that our serializer takes our JSON format and converts it into a format that the browser can read. In the example below, we target all of our <a> tags and assign the correct href to them.

This would be the beginnings of us customizing our serializer for React:

<BlockContent blocks={post.body} serializers={{ 
	marks: { 
		link: props => {
			<a className="myCustomLinkClass" href={props.mark.href}>{props.children}</a>
		} 
	}
}} />

Portable Text and beyond

These are the 3 essential parts when getting started with Portable Text. From here, there are many things you can do with Portable Text.

Some of those include:

If you would like to share some of the content you create related to Portable Text or any Sanity content, feel free to share it in the Sanity Exchange Community. You can share guides, plugins, snippets, projects for others to learn and grow from.

Happy coding everyone! Thank you for reading.

Other guides by author

Build your first blog using React
- has 23 likes

Official(made by Sanity team)

Build a blog in React from scratch! We'll use Sanity for the content management and Tailwind CSS for styling.

Kapehe
Go to Build your first blog using React