WPGraphQL Content Blocks
This FREE plugin from the folks at QZ.com exposes a way to query HTML content from WordPress Posts and Pages as “Blocks” (not related to Gutenberg) to bring more structure to your queried content.
Community PluginView Plugin on Github
This WPGraphQL plugin returns a WordPress post’s content as a shallow tree of blocks and allows for some limited validation and cleanup. This is helpful for rendering post content within component-based front-end ecosystems like React.
This plugin adds a GraphQL field called
Post in WPGraphQL (and any other post types configured to appear in WPGraphQL).
blocks field contains a list of the root-level blocks that comprise the post's content. Each block is a distinct HTML element, embeddable URL, shortcode, or Gutenberg block (beta!).
For example, if a post’s
content field in GraphQL contained:
blocks field would contain:
When consuming this field, you can now easily iterate over the blocks and map them to components in your component library. No more
dangerouslySetInnerHTMLing your entire
An exhaustive GraphQL of a post’s
blocks field would look like this:
This will return a list of
BlockTypes, defined in
src/types/Blocktype.php. Let’s break down each of these fields:
BlockNameEnumType (defined in
The name of the block. For HTML blocks, this is the uppercase version of the HTML tag name, e.g.
TABLE etc. Shortcode and embed types are name-spaced with
EMBED_ respectively., e.g.
HTML block types are hardcoded, as are Gutenberg block types (for now). Embed types are determined by the handlers that have been registered in
global $wp_embed->handlers and shortcodes are determined by the handlers registered with
global $shortcode_tags. A complete list of permissible block names can seen by browsing the WPGraphQL schema.
You can filter the type definitions with
The suggested HTML tag name for the block. For HTML blocks, this is simply a lowercased version of the type field. For embeds and shortcodes, it will likely be
null. This field is most useful for Gutenberg blocks, as a hint from the server for which tag to use when wrapping the
innerHtml (see below).
The stringified inner content of the block. Can be passed into a React component using
dangerouslySetInnerHTML, for example.
Note that the value of
innerHtml is the stringified version of all the block’s descendants after they have been parsed. This means that any invalid tags, attributes, etc. will have been stripped out.
Type: List of
Each item in the list is a name/value pair describing an attribute of the block. For HTML blocks, these are taken from the HTML attributes, e.g.
Note that this field will only contain valid attributes for the given Block, as defined in
BlockDefinitions. Invalid attributes are stripped out during the parsing process (see How Parsing Works).
Type: List of
connections field returns an array of objects that are connected to the block. For example, if you wanted to upload an image and associate it with a block, that image could be queried as a GraphQL connection here. The
connections field will always be empty by default. Presumably there is some way to derive these connections from the block's attributes, but we have no way of knowing what that correspondence is. If you'd like to use this field, it's up to you to filter
graphql_blocks_output and populate the
connections array as you see fit.
A block is an atomic piece of content that comprises an article body. We define a block as being an HTML tag (like a paragraph, list, table, etc), a Gutenberg block, a text node (the textual content of a non-empty HTML element or shortcode), a shortcode (like a caption, gallery, or video), or an embed (an embeddable URL on its own line in the post content).
An HTML block is an HTML element (typically a block-level element) represented by its tag name. If an HTML tag’s name is not included in
BlockNameEnumType, it will be stripped from the tree. Additionally, at runtime it must meet the requirements specified in the block definitions in order to be considered valid.
An example HTML block in a GraphQL response looks like this:
Gutenberg blocks map very well to blocks, but do not have a server-side registration system. Like HTML blocks, we are forced to hardcode a list of core Gutenberg blocks. This list can be extended with
graphql_blocks_definitions to add your own custom Gutenberg blocks.
post_content. While we are most interested in the
attributes of a block, the
innerHtml is also important since the rendered tag name could be important information to, say, a React component tasked with its implementation.
For this reason, we descend into the
innerHtml of a Gutenberg block to extract the
tagName of the surrounding tag, then discard it, leaving just the true "inner" HTML of the block.
In the example above, this allows the
innerHtml to be
My Heading instead of
<h2>My Heading</h2>. This is a much better situation for the components that implement this data.
Gutenberg blocks present a number of challenges and the spec is still evolving. Take care when using this plugin with Gutenberg blocks since there will likely be breaking changes ahead.
A shortcode block is a WordPress shortcode. Shortcode/embed blocks are returned untransformed: the parsing of shortcodes is the responsibility of the front-end consuming the GraphQL endpoint. Only the name of the shortcode, its attributes and any nested content of the shortcode are returned in the GraphQL response.
Shortcode block type names are prefixed with the
SHORTCODE_ namespace by default.
An embed is a distinct block-type that represents WordPress’ URL-to-markup embedding functionality. If WordPress recognizes a URL as an embed, this plugin will output it as an embed block.
Embed block type names are prefixed with the
EMBED_ namespace by default.
Because neither shortcode or embed blocks are parsed, the markup for embedding the URL is not provided by the plugin.
We can specify validation requirements for individual blocks. This allows us to enforce certain rules about blocks that determine where they end up in the tree, what attributes they may have, and whether or not they should end up in the GraphQL response at all.
Block definitions (and the default definition from which all blocks extend) for blocks can be found in
src/types/shared/BlockDefinitions. If you are interested in filtering the block definitions (via
graphql_blocks_definitions) to override behavior or add your own block definitions, you should look over that file.
Our default block definition suits us well for most block HTML elements; we always want them to exist at the root and we will hoist them to the root if we find them nested deeper in the post content HTML. We therefore don’t provide any overrides for most block elements:
<p> tags to live at the root, but we do not enforce it (i.e. we don’t want to hoist a
<p> tag out of a parent element) so we use this definition.
We don’t want inline HTML elements like
<a> to exist by themselves at the root, and we don’t want to permit the
target attribute, so we use the following definition:
<a> tag found at the root-level of the post HTML will be wrapped in a
<p> tag before being added to the tree. Additionally, if there is a
target attribute, it will not appear in the
Here’s a rough breakdown of the process of parsing post content into blocks. (If the block is a Gutenberg block, we use
gutenberg_parse_blocks and skip these steps.)
- The post content string is prepared for parsing (see
src/data/Fields.php). This includes running the
- The prepared content string is loaded into a PHP DOMDocument) object. This allows us to recurse the HTML as a tree.
DOMDocumentobject is passed into an
src/parser/class-htmlblock.php) object. This begins the process of recursing the tree. Each child block is assigned a class depending on its type:
ShortcodeBlock. Each block is responsible for validating itself against the Block Definitions (
src/types/shared/BlockDefinitions.php) to determine whether it belongs in the tree or not.
- Although the tree is recursed and validated to an infinite depth, the GraphQL type
src/types/BlockType.php) will stringify the tree below a depth of 1 for consumption in the GraphQL endpoint.
Given a query for the content of a post returns the following:
Then we would expect a query for the blocks that comprise the post to return the following.
We can also see the attributes for the shortcode and embed blocks by requesting the