Skip To Main Content

Get started with WPGraphQL Content Blocks

WPGraphQL Content Blocks is a plugin that allows querying Gutenberg blocks using WPGraphQL.

Prerequisites

In order to follow this tutorial, the following should be installed and activated prior to completing this tutorial. All three can be done following the Get Started with Faust guide.

NOTE
WPGraphQL is the only dependency required to use WPGraphQL Content Blocks, but the others are needed to follow this tutorial.

WordPress Plugins
  1. WPGraphQL
  2. Faust WordPress Plugin
NPM Packages

The following NPM package should be added as a dependency to the NextJS project.

  1. FaustJS

Getting started

Install the plugin

  1. Head over to the GitHub repo and download the latest version of the wp-graphql-content-blocks plugin from the releases tab.
  2. Upload the plugins .zip file to your WordPress site via the plugins page or unzip and copy the file contents into your WordPress wp-content/plugins folder manually.
  3. Activate the plugin in the WordPress plugins page:
The plugin activation page in WordPress

GraphiQL IDE Exploration

Once the plugin is installed and activated, head over to the GraphiQL IDE. You can find it in the WordPress toolbar.

You should be able to perform queries for the block data. There is a new field added in the Post and Page models called editorBlocks. This represents a list of available blocks for that content type:

Example query for editorBlocks using GraphiQL IDE

If you search in GraphiQL’s documentation explorer tab for the editorBlocks type you will be able to see the available block fields. The most important ones are:

  • renderedHTML: It’s the HTML of the block as rendered by the render_block function.
  • name: The actual name of the block taken from its block.json spec.
  • __typename: The type of block transformed from the name field in camel-case notation.
  • apiVersion: The apiVersion of the block taken from its block.json spec.
  • innerBlocks: The inner block list of that block.
  • isDynamic: Whether the block is dynamic or not, taken from its block.json spec.
  • clientIdparentClientId: Unique identifiers for the block and the parent of the block. We will explain their usage later.

How does the plugin work?

The plugin iterates over the block.json types as registered within WordPress and creates WPGraphQL types and resolvers. As long as your blocks use the register_block_type function passing a block.json, it will be available in the system without any extra steps.

NOTE
In WordPress block development, a block.json file is a configuration file that provides metadata and settings for a block. It defines the block’s name, title, icon, category, and other settings related to its appearance and behavior. For more information, see the section on block.json files in the WordPress Developer’s Handbook.

As an example, given the following block.json definition of a block:

// block.json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 2,
  "name": "my-plugin/notice",
  "icon": "star",
  "version": "1.0.3",
  "attributes": {
    "message": {
      "type": "string",
      "source": "html",
      "selector": ".message"
    }
  }
}
Code language: JSON / JSON with Comments (json)

The plugin will create the following WPGraphQL type:

type MyPluginNotice {
    attributes: MyPluginNoticeAttributes;
}
type MyPluginNoticeAttributes {
    message: String;
}
Code language: CSS (css)

When you request to resolve the message attribute for MyPluginNotice, the plugin will use a resolver that tries to extract the field by sourcing the text element using the selector. As an example, with the following HTML:

<div class="message">Hello World</div>Code language: HTML, XML (xml)

Since the block.json message attribute uses the .message class selector to source the text for that field, this will resolve to:

"Hello World"

Attribute Types

Currently the plugin handles the following attribute types taken from the reference list:

  • boolean
  • number
  • integer
  • string
  • object
  • array

NOTE
If you see a specific attribute missing that you need for your project, please open a new Feature Request so that the Faust team can make it available for you.

How do I query block data?

To query specific block data you need to define that data in the contentBlock as the appropriate type. For example, to use CoreParagraph attributes you need to use the following query:

{
  posts {
    nodes {
      editorBlocks {
        __typename
        name
        ... on CoreParagraph {
          attributes {
            content
            className
          }
        }
      }
    }
  }
}

If the resolved block has paragraph blocks, it will return the relevant fields, otherwise it will return null.

{
  "__typename": "CoreParagraph",
  "name": "core/paragraph",
  "attributes": {
    "content": "Hello world",
    "className": null
  }
}
Code language: JSON / JSON with Comments (json)

What about innerBlocks?

All the blocks available (both blocks and innerBlocks) will be combined and returned as a flat list by default.

For example, given the following HTML Content:

<columns>
  <column>
    <p>Example paragraph in Column</p>
  </column>
</columns>
Code language: HTML, XML (xml)

It will resolve the following block list data:

[
  {
    "__typename": "CoreColumns",
    "name": "core/columns",
    "id": "63dbec9abcf9d",
    "parentId": null
  },
  {
    "__typename": "CoreColumn",
    "name": "core/column",
    "id": "63dbec9abcfa6",
    "parentId": "63dbec9abcf9d"
  },
  {
    "__typename": "CoreParagraph",
    "name": "core/paragraph",
    "id": "63dbec9abcfa9",
    "parentId": "63dbec9abcfa6",
    "attributes": {
      "content": "Example paragraph in Column 1",
      "className": null
    }
  }
]
Code language: JSON / JSON with Comments (json)

The CoreColumns contains one or more CoreColumn blocks, and each CoreColumn contains a CoreParagraph. Notice that all the blocks are returned as a flat list.

If you want to maintain the hierarchy without returning a flat list, you want to add flat: false in the query and nest the block types:

editorBlocks(flat: false) {
    __typename
    name
    ...on CoreColumns {
        innerBlocks {
            ...on CoreColumn {
                ...on CoreParagraph {
                    attributes {
                        content
                        className
                    }
                }
            }
        }
    }
}
Code language: JavaScript (javascript)

When not using a flat list it can become a scalability issue since you don’t really know how many levels deep the query must traverse to resolve all block levels. It could be 2 levels or it could be 50. Using block patterns for example, poses a real issue since it may contain a deep hierarchical structure. In each case we would have to copy the whole fragment list again and again.

This is an example of how you can request all blocks as a flat list:

editorBlocks {
    __typename
    name
    id: clientId
    parentClientId
    ... on CoreColumns {
        attributes {
            className
        }
    }
    ... on CoreColumn {
        attributes {
            className
        }
    }
    ...on CoreParagraph {
        attributes {
            content
            className
        }
    }
}

This will traverse the hierarchy of blocks and put them back in a single list.

Recombining a Flattened List

Given the flattened list of blocks though, how can you put it back together? Well that’s where you use the clientId and parentClientId fields to assign temporary unique ids for each block.

The clientId field assigns a temporary unique id for a specific block and the parentClientId will be assigned only if the current block has a parent. If the current block does have a parent, it will get the parent’s clientId value.

So to put everything back in its hierarchical order for Headless usage, you want to use the flatListToHierarchical function as mentioned in the WPGraphQL docs. This is available in the @faustwp/core package and it is used to transform a flat list into a tree structure.

This way you can reconstruct the block tree as before and pass it on to the WordPressBlocksViewer component to display it:

import { WordPressBlocksViewer } from '@faustwp/blocks';
import { flatListToHierarchical } from '@faustwp/core';
...
const { editorBlocks } = props.data.post;
const blocks = flatListToHierarchical(editorBlocks);

<WordPressBlocksViewer blocks={blocks} />
...
Code language: JavaScript (javascript)

NOTE
Currently the clientId field is only unique per request and is not persisted anywhere. If you perform another request each block will be assigned a new nodeId each time.

The flatListToHierarchical method has the following signature:

function flatListToHierarchical(
  data: Data[] = [],
  {
    idKey = 'id',
    parentKey = 'parentId',
    childrenKey = 'children',
  }: Params = {},
)

export interface Params {
  idKey?: string;
  parentKey?: string;
  childrenKey?: string;
}

type Data = Record<string | number, unknown>;
Code language: TypeScript (typescript)

It accepts a list of Data items that contain arbitrary objects. Each object needs to contain two properties. One each for the unique id and parentId fields. The parentId should point to an id of the parent Data item.

The second parameter is used to configure which fields to use to configure the id and parentId fields if they are different than the default. The last parameter childrenKey is used for placing all the children Data items in the result set.

For example when given the following list of items:

const items = [
  { id: '1', name: 'abc', parentId: '2' },
  { id: '2', name: 'abc', parentId: '' },
  { id: '3', name: 'abc', parentId: '5' },
  { id: '4', name: 'abc', parentId: '2' },
  { id: '5', name: 'abc', parentId: '' },
  { name: 'abc', parentId: '' },
  { id: '6', name: 'abc', parentId: '2' },
  { id: '7', name: 'abc', parentId: '6' },
  { id: '8', name: 'abc', parentId: '6' },
];Code language: JavaScript (javascript)

The result of calling flatListToHierarchical with the default parameters will be:

{
    id: '2',
    name: 'abc',
    parentId: '',
    children: [
      { id: '1', name: 'abc', parentId: '2', children: [] },
      { id: '4', name: 'abc', parentId: '2', children: [] },
      {
        id: '6',
        name: 'abc',
        parentId: '2',
        children: [
          { id: '7', name: 'abc', parentId: '6', children: [] },
          { id: '8', name: 'abc', parentId: '6', children: [] },
        ],
      },
    ],
  },
  {
    id: '5',
    name: 'abc',
    parentId: '',
    children: [{ id: '3', name: 'abc', parentId: '5', children: [] }],
  },Code language: CSS (css)

How to render blocks from the @faust/blocks package?

The @faust/blocks package contains a small reference list of blocks that you can use in your site. To use them, you need to import the relevant blocks into your block list:

// wp-blocks/index.js
import { CoreBlocks } from '@faustwp/blocks';

export default {
  CoreParagraph: CoreBlocks.CoreParagraph,
  CoreColumns: CoreBlocks.CoreColumns,
  CoreColumn: CoreBlocks.CoreColumn,
  CoreCode: CoreBlocks.CoreCode,
  CoreQuote: CoreBlocks.CoreQuote,
  CoreImage: CoreBlocks.CoreImage,
  CoreSeparator: CoreBlocks.CoreSeparator,
  CoreList: CoreBlocks.CoreList,
  CoreButton: CoreBlocks.CoreButton,
  CoreButtons: CoreBlocks.CoreButtons,
  CoreHeading: CoreBlocks.CoreHeading,
};
Code language: JavaScript (javascript)

Then in your template query you need to pass the provided fragment entries:

// wp-templates/front-page.js
import blocks from '../wp-blocks';

Component.query = gql`
  ${blocks.CoreParagraph.fragments.entry}
  ${blocks.CoreColumns.fragments.entry}
  ${blocks.CoreColumn.fragments.entry}
  ${blocks.CoreCode.fragments.entry}
  ${blocks.CoreButtons.fragments.entry}
  ${blocks.CoreButton.fragments.entry}
  ${blocks.CoreQuote.fragments.entry}
  ${blocks.CoreImage.fragments.entry}
  ${blocks.CoreSeparator.fragments.entry}
  ${blocks.CoreList.fragments.entry}
  ${blocks.CoreHeading.fragments.entry}
  query GetPage(
    $databaseId: ID!
    $asPreview: Boolean = false
  ) {
    page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) {
      title
      content
      editorBlocks {
        name
        __typename
        renderedHtml
        id: clientId
        parentId: parentClientId
        ...${blocks.CoreParagraph.fragments.key}
        ...${blocks.CoreColumns.fragments.key}
        ...${blocks.CoreColumn.fragments.key}
        ...${blocks.CoreCode.fragments.key}
        ...${blocks.CoreButtons.fragments.key}
        ...${blocks.CoreButton.fragments.key}
        ...${blocks.CoreQuote.fragments.key}
        ...${blocks.CoreImage.fragments.key}
        ...${blocks.CoreSeparator.fragments.key}
        ...${blocks.CoreList.fragments.key}
        ...${blocks.CoreHeading.fragments.key}
      }
    }
  }
`;
Code language: JavaScript (javascript)

Now that you have all the queries ready, you can render the blocks using the provided flatListToHierarchical method and WordPressBlocksViewer:

// wp-templates/front-page.js

import { gql } from '@apollo/client';
import { flatListToHierarchical } from '@faustwp/core';
import { WordPressBlocksViewer } from '@faustwp/blocks';
import blocks from '../wp-blocks';

export default function Component({ loading, data }) {
  // Loading state for previews.
  if (loading) {
    return <>Loading...</>;
  }

  const { title, editorBlocks } = data?.page ?? { title: '' };
  const blockList = flatListToHierarchical(editorBlocks, { childrenKey: 'innerBlocks' });

  return (
    <div className='is-layout-constrained'>
      <h1>{title}</h1>
      <WordPressBlocksViewer blocks={blockList} />
    </div>
  );
}
...




Code language: JavaScript (javascript)

WordPressBlocksProvider- Theme Use

NOTE
If you do not need a theme, you must pass null as the theme prop for the WordPressBlocksProvider like below. If you don’t pass a theme, it will fail.

<WordPressBlocksProvider config={{ blocks, theme: null }}>Code language: HTML, XML (xml)