How to dynamically inject blocks into content

A common requirement that my clients have is a content injection plugin for a client. This is because content injection is a common need in the publishing space.

What is content injection?

Let’s say for example you want to have ads on your website, like this:

But of course you don’t want the ads in the content itself. So the editor view still looks like this:

To achieve this, the content injection plugin filters the content, adding the ads when needed.

This is done on-the-fly, so whenever the content is output.

Challenges with content injection

I’ve built two such injection plugins with the Classic Editor, and there were two main challenges.

  1. Building a user interface for managing the content to inject. This usually meant cobbling together a settings screen using CMB2.
  2. How to find out where to inject the content. In the past, all post content in WordPress was HTML.

So you had to rely on parsing this HTML in PHP. A process that is not only quite unreliable, but also resource-intensive.

But with blocks, this is a lot easier.

Creating content to inject

Gone are the days of having to build a custom interface to create the ads. Here are the steps I took:

  1. Register a custom post type called Ads.
  2. Implement a post content template. This template ensures that every new Ad post contains an ad block in the editor.
  3. Lock the post content template. Now users cannot delete or move the ad block.
  4. Remove any other block types from the inserter. So users can’t add any other blocks to the Ad post content.

This means that users can now only configure the ad unit. While the screenshot below is not the actual ad block, it still gives an idea of the setup.

Below the post content editor, I added a JavaScript driven meta box to configure the rules for the ad injection. The individual configurations are stored in post meta of the specific ad post.

This maybe sounds complicated, but it isn’t. Since the metabox is part of the block editor, I can use the same APIs as I do if I were to build a custom block.

Mastering custom block development gives you the foundations you need to make the most of modern WordPress.

And with more and more of WP Admin becoming JavaScript driven, I recommend that you learn custom block development now.

How block content is modified

Now that we got our ad, we need to inject it into the post content.

For that we need to do three things:

  1. Parse the blocks in the content.
  2. Loop through the blocks and inject the ads in the right slots.
  3. Serialize all blocks, and return the modified content.

Okay so what does this mean?

Let’s take this block as an example:

In the database, this paragraph block looks like this:

This is the serialized form of the block. Serialization is the process of converting data structures into a format that is easy to store. And this is the format that we get when we use the `the_content` filter.

But with blocks, we now can parse them. Parsing means that we take a data structure, and transform it into a more useful format. In our case an array.

So here is the parsed content of a post that has only this sample paragraph in it.

So now that we have this, injecting our ads in the right spots is straightforward.

njecting the content

Here is a code snippet that injects the content of an Ad post after the second block of a post (the code is also available on Github):

<?php
function wpdc_inject_ads_into_content( string $content ) : string {
  // Only target single views, and only the posts in the main loop.
  if ( ! is_singular() || ! in_the_loop() ) {
    return $content;
  }

  // This gets the post content of our Ad post type instance
  $ad = wpdc_get_ad_for_post();

  // Parse the blocks in the content into an array.
  $parsed_content = parse_blocks( $content );

  // Filter out empty blocks. Depending on spacing in the post content, there might be empty blocks.
  $parsed_content = array_filter(
    $parsed_content,
    function( $block ) {
      return ! empty( $block['blockName'] );
    }
  );

  // We need to re-index the array. Else if blocks were removed the keys wouldn't be in sequential order.
  $parsed_content = array_values( $parsed_content );

  // Which is the current block? We start at 0 in case an ad should be first thing in the post.
  $current_block_index = 0;
  // Here we store all original blocks from the content plus the injected ads.
  $modified_parsed_content = [];

  foreach ( $parsed_content as $block ) {
    $current_block_index++;
    $modified_parsed_content[] = $block;

    // As we did start at 0, slot 3 is actually index 2.
    if ( $current_block_index === 2 ) {
      // Here we inject the ad. Since the ad content is also serialized, we need to parse it before inserting.
      $modified_parsed_content[] = parse_blocks( $ad )[0];
    }
  }

  // After the parsing, back to the serialized form, as this is what `the_content` filter needs.
  $serialized_blocks = [];
  foreach ( $modified_parsed_content as $block ) {
    $serialized_blocks[] = serialize_block( $block );
  }

  return implode( '', $serialized_blocks );
}
add_filter( 'the_content', 'wpdc_inject_ads_into_content', 0 );

Taking it further

The code snippet above is a simplified version of what I wrote in the plugin. But it contains all the important pieces.

You could of course take it further. One example would be to only count paragraph blocks during injection. You can do that, because the parsed blocks contain the block type.

You could also look at the next and previous blocks before injecting anything. This is useful for example if you want to avoid injecting ads between a heading and the next paragraph.

And of course this was but one example for modifying block content. As long as you follow the parse, modify, serialize approach, any other changes will work as well.

Fränk Klein Avatar