Getting started with wp-graphql-content-blocks
It is recommended that you install wp-graphql-content-blocks
plugin that allows querying gutenberg blocks with wp-graphql.
This should be used in conjunction with @faustwp/blocks
package as a lot of conventions we use are designed with those tools in mind.
This plugin is similar to the wp-graphql-gutenberg
but it is actively maintained and tackles some scalability issues of the former.
Introduction
Head over to the github repo and dowload the plugin as a zip file.
Install the plugin into your wp-content/plugins
on a brand new WordPress installation.
Within the wp-graphql-content-blocks-main
plugin folder use composer to install the vendor dependencies:
composer install
Activate the plugin within WordPress plugins page:

There is no other configuration needed once you install the plugin.
Getting started
Once the plugin is installed, head over to the GraphiQL IDE and you should be able to perform queries for the block data (This plugin is an extension of wp-graphql, so make sure you have it installed first.).
There is a new field added in the Post and Page models called contentBlocks
.
This represents a list of available blocks for that content type:

If you search in GraphiQL's documentation explorer tab for the contentBlocks
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 it'sblock.json
spec.__typename
: The type of block transformed from thename
field in camel-case notation.apiVersion
: The apiVersion of the block taken from itsblock.json
spec.innerBlocks
: The innerblock list of that block.isDynamic
: Whether the block is dynamic or not, taken from itsblock.json
spec.nodeId
,parentId
: Unique identifiers for the block and the parent of the block. We will explain their usage later.
How does the plugin work?
The plugin essentially iterates over the whole list of block.json
types as registered within WordPress
and creates WPGraphQL types and resolvers based on that specification.
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.
As an example, we are given the following block.json
definition of a block:
{
"$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"
}
}
}
The plugin will create the following WPGraphQL type:
type MyPluginNoticeAttributes {
message: String;
}
type MyPluginNotice {
attributes: MyPluginNoticeAttributes;
}
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
. For example, given the following HTML:
<div class="message">Hello World</div>
Since the block.json
message attribute uses the .message
selector to source the text for that field, this will resolve to:
"Hello World"
Currently the plugin handles the following attribute types taken from the reference list:
- boolean
- number
- integer
- string
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 {
contentBlocks {
__typename
name
...on CoreParagraph {
attributes {
content
className
}
}
}
}
}
}
If the resolved block has values for those fields, it will return them, otherwise it will return null
.
{
"__typename" : "CoreParagraph",
"name": "core/paragraph",
"attributes": {
"content": "Hello world",
"className": null
}
}
What about innerBlocks?
In order to facilitate querying innerBlocks
fields more efficiently you want to use contentBlocks(flat: true)
instead of contentBlocks
.
By passing this argument, all the blocks available (both blocks and innerBlocks) will be returned all flattened in the same list.
For example, given the following HTML Content:
<columns>
<column>
<p>Example paragraph in Column<p>
</column>
<column>
It will return the following blocks:
[
{
"__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
}
}
]
The CoreColumns
contains one or more CoreColumn
block, and each CoreColumn
contains a CoreParagraph
.
If we were to create a query to resolve this hierarchy, we would have to use:
contentBlocks {
__typename
name
...on CoreColumns {
innerBlocks {
...on CoreColumn {
...on CoreParagraph {
attributes {
content
className
}
}
}
}
}
}
This clearly becomes a scalability issue as we don't really know how many levels deep we must traverse for those types. It could be two levels or it could be 50. Using block patterns for example, poses a real issue.
In each case we would have to copy the whole fragment list again and again. This is one of the limitations of the wp-graphql-gutenberg
plugin, but with this plugin we have a more elegant solution.
This is how you request all blocks as a flat list:
contentBlocks(flat:true) {
__typename
name
id: nodeId
parentId
... on CoreColumns {
attributes {
className
}
}
... on CoreColumn {
attributes {
className
}
}
...on CoreParagraph {
attributes {
content
className
}
}
}
Given the flattened list of blocks though, how can you put it back? Well that's where you use the
nodeId
and parentId
fields to assign temporary unique ids for each block.
The nodeId
field assigns a temporary unique id for a specific block and the parentId
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 nodeId
value.
So in order to put everything back in the Headless site, you want to use the flatListToHierarchical
function as mentioned in the WPGraphQL docs.
This way you can reconstruct the block tree as before and pass it on to the WordPressBlocksViewer
component:
import { WordPressBlocksViewer } from '@faustwp/blocks';
import flatListToHierarchical from '../utilities/flatListToHierarchical';
...
const { contentBlocks } = props.data.post;
const blocks = flatListToHierarchical(contentBlocks);
<WordPressBlocksViewer contentBlocks={blocks} />
...
Currently the nodeId
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.