Access block data with PHP using parse_blocks() and render_block()

The Gutenberg block editor organizes your content into discrete blocks, and WordPress includes functions for easily accessing the individual blocks within a post.

Here’s a quick summary of how the functions work, and examples of them in use.

Jump to Section:

  1. parse_blocks()
  2. render_block()
  3. Display blockquote from post
  4. Table of contents from headings
  5. ACF block data

parse_blocks()

This function converts the HTML comments and markup stored in post_content into an array of parsed block objects.

Usage: $blocks = parse_blocks( $post->post_content );

The post_content of my Style Guide looks like:

<!-- wp:paragraph {"fontSize":"large"} -->
<p class="has-large-font-size">Lorem <strong>ipsum</strong> dolor <em>sit amet</em>, consectetur <a class="map-link" href="https://maps.google.com/?q=Houston,+TX">adipiscing elit</a>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":1} -->
<h1>Heading 1</h1>
<!-- /wp:heading -->

After running it through parse_blocks(), it looks like:

Array
(
    [0] => Array
        (
            [blockName] => core/paragraph
            [attrs] => Array
                (
                    [fontSize] => large
                )

            [innerBlocks] => Array
                (
                )

            [innerHTML] => 
<p class="has-large-font-size">Lorem <strong>ipsum</strong> dolor <em>sit amet</em>, consectetur <a class="map-link" href="https://maps.google.com/?q=Houston,+TX">adipiscing elit</a>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>

            [innerContent] => Array
                (
                    [0] => 
<p class="has-large-font-size">Lorem <strong>ipsum</strong> dolor <em>sit amet</em>, consectetur <a class="map-link" href="https://maps.google.com/?q=Houston,+TX">adipiscing elit</a>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>

                )

        )

    [1] => Array
        (
            [blockName] => 
            [attrs] => Array
                (
                )

            [innerBlocks] => Array
                (
                )

            [innerHTML] => 


            [innerContent] => Array
                (
                    [0] => 


                )

        )

    [2] => Array
        (
            [blockName] => core/heading
            [attrs] => Array
                (
                    [level] => 1
                )

            [innerBlocks] => Array
                (
                )

            [innerHTML] => 
<h1>Heading 1</h1>

            [innerContent] => Array
                (
                    [0] => 
<h1>Heading 1</h1>

                )

render_block()

This function takes a single parsed block object and returns the rendered HTML for that block.

Usage: echo render_block( $block );

Note: While this function does convert the block object into the rendered HTML, it does not run all the additional filters that are typically applied to the content, like converting lines to paragraphs, rendering shortcodes, and rendering embeds.

In many cases, you’ll want to use the the_content filter to add these extras. Example: echo apply_filters( 'the_content', render_block( $block ) );

Display blockquote from post

On my portfolio archive page, every 4th project includes a blockquote. You could pull this directly from the content of the portfolio item by looping through the blocks and displaying the core/quote block, if one is found.

Add this function to your theme, then call be_display_post_blockquote() to display the quote.

/**
 * Display blockquote from post 
 * @link https://www.billerickson.net/access-gutenberg-block-data/
 */
function be_display_post_blockquote() {
  global $post;
  $blocks = parse_blocks( $post->post_content );
  foreach( $blocks as $block ) {
    if( 'core/quote' === $block['blockName'] ) {
      echo render_block( $block );
      break;
    }
  }
}

Table of contents from headings

In a previous tutorial I showed how to dynamically build a table of contents by parsing the HTML.

Converting the HTML to blocks makes this much easier. You loop through the blocks and find the core/heading ones.

The example below makes a simple ordered list of all the headings in the page. You could take this a step further and use $block['attrs']['level'] to make a nested list based on the heading level (ex: h3 is subordinate to h2).

/**
 * List post headings 
 * @link https://www.billerickson.net/access-gutenberg-block-data/
 */
function be_list_post_headings() {
	global $post;
	$blocks = parse_blocks( $post->post_content );
	$headings = array();
	foreach( $blocks as $block ) {
		if( 'core/heading' === $block['blockName'] )
			$headings[] = wp_strip_all_tags( $block['innerHTML'] );
	}
	if( !empty( $headings ) ) {
		echo '<ol class="table-of-contents">';
		foreach( $headings as $heading )
			echo '<li>' . $heading . '</li>';
		echo '</ol>';
	}
}

Unfortunately WordPress does not store the heading’s ID as an anchor attribute, so you’ll still need to manipulate the HTML if you want to create anchor links (example).

ACF block data

If you have built a custom block with Advanced Custom Fields, you can easily access all the block data using this method.

ACF generates dynamic blocks. Rather than storing actual HTML, it stores all of the block data in the HTML comments and then dynamically renders it using the PHP file or function you specify.

I’m working on a site that includes a “Service” block. It has fields for title, anchor link, content, and “success story” (a linked post from elsewhere).

The homepage includes links to the top-level pages, as well as anchor links to each individual service block. I’m using parse_blocks() to find all of the acf/service blocks, then building the links using the $block['attrs'] data.

/**
 * Get service sections
 * @link https://www.billerickson.net/access-gutenberg-block-data/
 */
function ea_get_service_sections() {

	$sections = array();

	global $post;
	$blocks = parse_blocks( $post->post_content );
	foreach( $blocks as $block ) {
		if( 'acf/service' !== $block['blockName'] )
			continue;

		$title = $anchor = '';
		if( !empty( $block['attrs']['data']['title'] ) )
			$title = $block['attrs']['data']['title'];

		if( !empty( $block['attrs']['data']['anchor'] ) )
			$anchor = $block['attrs']['data']['anchor'];

		if( empty( $anchor ) )
			$anchor = $title;

		$sections[] = '<a href="' . get_permalink() . '#' . sanitize_title_with_dashes( $anchor ) . '">' . esc_html( $title ) . '</a>';
	}

	if( empty( $sections ) )
		return;

	echo '<ul class="service-sections">';
	foreach( $sections as $section )
		echo '<li>' . $section . '</li>';
	echo '</ul>';
}

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. Jo Waltham says

    Hi Bill, thanks for this tutorial. What hook are you running these functions on? I’m trying to create an acf block for events and I want to save the event date as post meta so I can use it to sort the events by date on the archive page. I’ve tried using the acf/pre_save_block filter but the post ID isn’t available at that point and therefore I can’t save to post meta. I’m wondering if I could use parse_block instead?

    • Bill Erickson says

      I recommend reaching out to Elliot / ACF Support to see if they have a recommended method for saving meta in an ACF block yet.

      But yes, you could probably use this approach to save the meta. Hook onto `save_post`, parse the blocks in post_content, find the event block, then `update_post_meta()`.

  2. RH says

    Hi Bill,

    Great post, again! And so timely!! Here’s a poser for you…..?

    ACF is really great at rendering its own data in the admin / Gutenberg UI, so e.g. if you have an ACF image field, and include that in the rendered callback template, the image shows in real time. Nice!

    However, if you want pull in data, e.g. say the featured image, as a background to a custom block that also wants to pull in the page “title”, how would you envisage displaying the “data” from these fields in real-time in the Gutenberg editor?

    At present, these are not being pulled in dynamically and therefore not showing on the “back end” but ARE showing on the FE…. which is a bore!! Any thoughts would be appreciated – code below for reference ;¬)

    function xx__page_title_render_callback( $payload_page_title ) {

    global $post;

    // get ACF data
    $xx__page_title = get_the_title();
    $xx__page_title_bkgd_image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), ‘full’ );
    $xx__page_title_text = get_field( ‘xx__page_title_text’ );

    // create id attribute for specific styling
    $id = ‘title-hero-‘ . $payload_page_title[‘id’];

    // create align class (“alignwide”) from block setting (“wide”)
    $align_class = $payload_page_title[‘align’] ? ‘align’ . $payload_page_title[‘align’] : ”;

    ?>

    <section class=”xx_page-title bk_page-title__hero-bkgd container-fluid”
    style=”background-image: url();”>

    <?php

    }

    • Bill Erickson says

      I would use JavaScript in the editor load this information.

      You could use the WordPress JS API and the ACF JS API to retrieve the data and update blocks.

  3. scsskid says

    great post, I think in

    archive-portfolio-featured.php line 9 it should be `$blocks = parse_blocks( $post->post_content );`

  4. Alex Vasquez says

    Hey Bill,
    Amazing. Saved the day for me. =)

    So I’m trying to render an acf block outside of the content area, which I’ve managed to do. Yay. But how do I prevent that block from ALSO displaying in the content.

    • Bill Erickson says

      Hmm, you could try using the render_block filter. Something like this might work:

      add_filter( 'render_block', function( $block_content, $block ) {
      	global $wp_query;
      	if( 'be/my_custom_block' === $block['blockName'] && 0 === $wp_query->current_post )
      		$block_content = '';
      	return $block_content;
      }, 10, 2 );
      

      The $wp_query->current_post is set to -1 outside the loop and 0 inside the loop, so that could be a way to conditionally display it. But this will only work if your “outside the content area” is outside the loop.

      You could also check a global variable that you define to figure out if it should appear or not.

  5. Karim says

    Hi Bill,

    Thanks for this post.
    It is a great one and helped me a lot.
    I wonder if there is a way to add blocks programmatically to posts?

    • Bill Erickson says

      You probably could using the parse_blocks or the_content filters.

      What I typically do is register a “Block Areas” post type, create an “After Post” post, then insert that after the post appears ( ea_block_area()->show( 'after-post' ); ).

      This way the client can edit what blocks are automatically added to the bottom of all posts.

  6. Isaac Fennell says

    Hi Bill, great article!

    Is it possible to filter a block’s content in order to parse any shortcodes in the text?

    I’m aware of the shortcode block but I need them inline as they just return text.

    Many thanks!

    • Bill Erickson says

      Inline shortcodes should still work I think because do_shortcode() runs on the_content filter, which runs after the blocks have been rendered as HTML.

  7. Jake says

    Hi Bill

    The actual title of this post is _exactly_ what I need, however, I can’t seem to get it to work.

    I’m trying to create a block that will be a TOC for another block.

    How would I do that?

    • Bill Erickson says

      The approach shown above is for using block data outside of blocks.

      It’s probably not a great idea to build a block that’s dependent on another block. If I wanted to add a ToC to a specific block, I’d probably use the render_block filter to modify that block’s output to include the ToC.

  8. fab says

    Hello, great post !
    how do you use this to parse innerBlocks, because if you use columns in your layout, then the parse_blocks function is not working anymore !?
    Thank you,

    • Bill Erickson says

      parse_blocks() should automatically parse any inner blocks. I’m using it in my starter theme to see if the content includes an h1 block, either as a top level block or inside another block (ex: columns). See the code here.