Was this page helpful?

What is rich text?

Rich Text is a field type that enables authors to create rich text content, similar to traditional "What you see is what you get" (wysiwyg) editors. The key difference here is that the Contentful Rich Text Field (RTF) response is returned as pure JSON rather than HTML. It offers common text formatting options such as paragraphs, lists and blockquotes, and allows entries and assets from Contentful to be linked dynamically and embedded within the flow of the text.

Rich Text on the Web App

The menu bar at the top of the Rich Text editor provides authors with all the usual text formatting capabilities, including creating links to a static URL, and inserting links to Contentful entries and assets from within the same Contentful space or entries from other spaces with cross-space references.

The Embed Entry button (top right) embeds a Contentful entry as a block or inline element.

Customization

A key aspect of Rich Text is the possibility to customize the field in a way that authors are limited to using specified formatting options.

For example, you can limit the formatting options to only include paragraph tags, or limit the kinds of entries that can be hyperlinked or embedded.

A screenshot of the Rich Text editor, showing the differences in the top toolbar with formatting options adjusted

Customization can be done on the Web App or through the API.

Rich Text on the API Response

The RTF API response is returned as a JSON array of nodes that follows the format of an abstract syntax tree.

The following is an example of a RTF REST API response which returns a paragraph — "This text is important" — with the word "important" marked as bold:

{
  "nodeType": "document",
  "data": {},
  "content": [
    {
      "nodeType": "paragraph", // Can be paragraphs, images, lists, embedded entries
      "data": {},
      "content": [
        {
          "nodeType": "text",
          "value": "This text is ",
          "data": {},
          "marks": []
        },
        {
          "nodeType": "text",
          "value": "important",
          "data": {},
          "marks": [
            "type": "bold"
          ]
        }
      ]
    }
  ]
}

Embedded and Linked Entries in Rich Text

Rich Text allows editors to link and embed entries in the flow of text in the UI. These links are returned in the RTF API response as references, and the referenced data is returned in a separate object (more on this below). As a developer, this gives you the flexibility on the front end to build out the HTML you need for linked assets and entries rather than having to deal with opinionated HTML and formatting from the API.

For example, you might want to:

  • Use custom anchor link wrappers, such as a React Router link or a NextJS Link in your single page application for inline links in Rich Text
  • Use the Contentful Images API to resize, crop and manipulate an image that is returned as a linked asset
  • Render a widget such as an image gallery, a product description box, a sign up form, an annotation window or anything else in the flow of the RTF!

The following screenshot shows a hyperlinked entry, followed by an embedded entry.

Screenshot of an embedded hyperlink and embedded entry in the Rich Text field editor

This is an example of an API response for a hyperlinked entry:

{
  data: {
    target: {
      sys: {
        id: "12345", // Linked entry ID
        type: "Link",
        linkType: "Entry"  // This is a linked entry — observe this is a reference with no data — yet!
      }
    }
  },
  content: [
    {
      marks: [],
      value: "the link text",
      nodeType: "text"
    }
  ],
  nodeType: "entry-hyperlink" # The nodeType tells us how the editor linked the entry
}

and this is an example of an API response for an embedded entry:

{
  data: {
    target: {
      sys: {
        id: "67890", # Linked entry ID
        type: "Link",
        linkType: "Entry" # This is a linked entry — observe this is a reference with no data — yet!
      }
    }
  },
  content: [
    {
      marks: [ ],
      value: "",
      nodeType: "text"
    }
  ],
    nodeType: "embedded-entry-inline" # The nodeType tells us how the editor linked the entry
}

Rendering Rich Text references using the REST API

Observe above that the linked entries in the flow of the RTF are returned as references with no accompanying data. The data for the linked entries referenced in the flow of the RTF are in a separate node that is returned from the REST API response: the includes object.

  • includes.Entry contains the data for the linked entries referenced in the RTF response
  • includes.Asset includes the data for the linked assets referenced in the RTF response (e.g. images and media)

Note that you have to add an extra header to include the data for entries linked from other spaces.

Here's an example of the includes node in the REST API response, alongside the items node that contains the requested fields RTF response, referencing the linked entries.

{
  "items": [
    {
      "fields": {
        "richText": {
          "content": [
            {
              "data": {
                "target": {
                  "sys": {
                    "id": "12345", // Find the entry with this ID in the includes["Entry"] object below
                    "type": "Link",
                    "linkType": "Entry"
                  }
                }
              },
              "content": [],
              "nodeType": "embedded-entry-block"
            },
            {
              "data": {
                "target": {
                  "sys": {
                    // Find the entry with this ID in the includes["Entry"] object below
                    "urn": "crn:contentful:::content:spaces/abcde/environments/master/entries/35678",
                    "type": "ResourceLink",
                    "linkType": "Contentful:Entry"
                  }
                }
              },
              "content": [],
              "nodeType": "embedded-resource-block"
            },
            {
              "data": {
                "target": {
                  "sys": {
                    "id": "67890", // Find the entry with this ID in the includes["Asset"] object below
                    "type": "Link",
                    "linkType": "Asset"
                  }
                }
              },
              "content": [],
              "nodeType": "embedded-asset-block"
            }
          ]
        }
      }
    }
  ],
  "includes": {
    "Entry": [
      {
        "sys": {
          "id": "12345", // Here we have all the data for the linked Entry inside the RTF response
          "type": "Entry",
          "contentType": {
            "sys": {
              "type": "Link",
              "linkType": "ContentType",
              "id": "videoEmbed"
            }
          }
        },
        "fields": {
          "title": "Example video embed",
          "embedUrl": "https://www.youtube.com/embed/12345"
        }
      },
      {
        "sys": {
          "id": "35678", // Here we have all the data for the linked Entry from a different space inside the RTF response
          "type": "Entry",
          "contentType": {
            "sys": {
              "type": "Link",
              "linkType": "ContentType",
              "id": "videoEmbed"
            }
          }
        },
        "fields": {
          "title": "Example video embed from a different space",
          "embedUrl": "https://www.youtube.com/embed/35678"
        }
      }
    ],
    "Asset": [
      {
        "sys": {
          "id": "67890", // Here we have all the data for the linked Asset inside the RTF response
          "type": "Asset"
        },
        "fields": {
          "title": "my-image",
          "description": "Image description",
          "file": {
            "url": "//images.ctfassets.net/.../example.jpg",
            "fileName": "example.jpg"
          }
        }
      }
    ]
  }
}

If you're using a Contentful client library to make a call to the Contentful REST API, those linked assets and entries in the RTF will be resolved for you. This means that the data will be inserted into the flow of the RTF, allowing you to access e.g. richText.content[0].data.target.fields.file.url as expected in the example above.

To understand how the client libraries resolve links in the RTF, take a look at the contentful-resolve-response package, which converts the flat nodes into a rich tree of data when using the JavaScript client library.

To sum up:

  • Hyperlinking an entry means to render an anchor link that points towards the URL of a separate entry
  • Embedding an entry means to render the contents of this linked Entry in the flow of text, whether on an inline or a block level
  • References to linked entries and assets are provided in the raw Rich Text response from the REST API
  • References to linked entries and assets are resolved for you if you're using a Contentful client library to make the request to Contentful

Rendering the Rich Text response from the REST API with linked assets and entries on the front end

Contentful provides you with tools to speed up your workflow on the front end and to allow you to work with the RTF data and render the nodes into HTML — RTF renderers. There are a number of RTF renderer packages available for your favorite programming languages and frameworks — view on GitHub.

The following code example uses a response from the REST API processed by the JavaScript client library, and renders it using the Contentful Rich Text React Renderer available from NPM to demonstrate the concepts in JavaScript and React.

The RTF response below includes a linked entry of type videoEmbed and a linked image asset.

import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { BLOCKS } from '@contentful/rich-text-types';

// Create a bespoke renderOptions object to target BLOCKS.EMBEDDED_ENTRY (linked entries e.g. videoEmbed
// and BLOCKS.EMBEDDED_ASSET (linked assets e.g. images)

function renderEntry(node, children) {
  if (node.data.target.sys.contentType.sys.id === 'videoEmbed') {
    return (
      <iframe
        src={node.data.target.fields.embedUrl}
        height="100%"
        width="100%"
        frameBorder="0"
        scrolling="no"
        title={node.data.target.fields.title}
        allowFullScreen={true}
      />
    );
  }
}

function renderAsset(node, children) {
  // render the EMBEDDED_ASSET as you need
  return (
    <img
      src={`https://${node.data.target.fields.file.url}`}
      height={node.data.target.fields.file.details.image.height}
      width={node.data.target.fields.file.details.image.width}
      alt={node.data.target.fields.description}
    />
  );
}

const renderOptions = {
  renderNode: {
    [BLOCKS.EMBEDDED_ENTRY]: renderEntry,
    [BLOCKS.EMBEDDED_RESOURCE]: renderEntry,
    [BLOCKS.EMBEDDED_ASSET]: renderAsset,
  },
};

export default function RichTextResponse({ richTextResponse }) {
  return <>{documentToReactComponents(richTextResponse, renderOptions)}</>;
}

Rendering the Rich Text response from the GraphQL API with linked assets and entries on the front end

The Contentful GraphQL API doesn’t require a client library to handle linked entries.

The RTF response from the GraphQL API is different and contains two top-level nodes.

"richTextResponse": {
  // JSON structure of the RTF
  "json": {
    # ...
  }
  // all referenced assets/entries
  "links": {
    # ...
  }
}

The references are not automatically resolved inside of the Rich Text JSON from the GraphQL API. This means we have to take a different approach to render and resolve links when using GraphQL.

You can still use documentToReactComponents from the rich-text-react-render: to render RTF data to the DOM, but instead of passing in an options object, you'll need to construct the object using a custom function to process some logic to resolve the links.

A common solution to rendering the embedded asset inline with the Rich Text response in JavaScript is to create a map of the assets (id: asset), and find the asset to display in the map when rendering an asset block.

import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { BLOCKS } from '@contentful/rich-text-types';

// Create a bespoke renderOptions object to target BLOCKS.EMBEDDED_ENTRY (linked entries e.g. videoEmbed)
// and BLOCKS.EMBEDDED_ASSET (linked assets e.g. images)

function renderOptions(links) {
  // create an asset block map
  const assetBlockMap = new Map();
  // loop through the assets and add them to the map
  for (const asset of links.assets.block) {
    assetBlockMap.set(asset.sys.id, asset);
  }

  // create an entry block map
  const entryBlockMap = new Map();
  // loop through the entries and add them to the map
  for (const entry of links.entries.block) {
    entryBlockMap.set(entry.sys.id, entry);
  }

  return {
    // other options...

    renderNode: {
      // other options...

      [BLOCKS.EMBEDDED_ENTRY]: (node, children) => {
        // find the entry in the entryBlockMap by ID
        const entry = entryBlockMap.get(node.data.target.sys.id);

        // render the entries as needed by looking at the __typename
        // referenced in the GraphQL query

        if (entry.__typename === 'VideoEmbed') {
          return (
            <iframe
              src={entry.embedUrl}
              height="100%"
              width="100%"
              frameBorder="0"
              scrolling="no"
              title={entry.title}
              allowFullScreen={true}
            />
          );
        }
      },
      [BLOCKS.EMBEDDED_ASSET]: (node, next) => {
        // find the asset in the assetBlockMap by ID
        const asset = assetBlockMap.get(node.data.target.sys.id);

        // render the asset accordingly
        return <img src={asset.url} alt="My image alt text" />;
      },
    },
  };
}

// Render richTextResponse.json to the DOM using
// documentToReactComponents from "@contentful/rich-text-react-renderer"

export default function RichTextResponse({ richTextResponse }) {
  return (
    <>
      {documentToReactComponents(
        richTextResponse.json,
        renderOptions(richTextResponse.links)
      )}
    </>
  );
}

Rules of Rich Text

  • Rich Text is provided as a JSON object of nodes.
  • Each node within a Rich Text document includes properties nodeType and data. Properties such as content, value and marks might appear depending on the value inserted on the document.
  • The document must have only one root node with nodeType: 'document'.
  • Nested documents are not allowed.
  • Nodes in the document can be of type Block, Inline or Text, see: type definitions, list of supported blocks, inlines.
  • The document can contain only nodes type of Block as direct children, and those must be one of a set of top-level blocks.
  • Blocks can contain nodes of type Block, Inline, or Text.
  • Some blocks must have only particular types of children. For example, ordered and unordered lists must have list items as children, tables must have table rows as children, and so on. See the list of block type relationships.
  • Void nodes are whitelisted. See the list of void nodes.
  • Inlines can contain nodes of type Inline or Text.
  • text nodes can have a list of associated marks, including bold, italic, underline, code, superscript, and subscript. See the list of supported marks.
  • Sibling Text nodes with equal marks should be grouped.
  • Custom node types and marks are not allowed.

Next steps

Not what you’re looking for? Try our FAQ.