Rendering plugin blocks the right way

Posted on 22nd Sep 2025
Takes about 5 mins to read
Hey, you seem to look at this article a lot! Why not Bookmark this article so you can find it easily in the future?

On a recent client website, I needed to programmatically load and render 3 blocks. Plugin blocks to be precise. In modern Drupal, there are 3 types of block you can load/render: 

  • Content blocks
  • Config blocks
  • Plugin blocks

And today we're interested in plugin blocks. 

Plugin blocks are blocks defined by the block plugin API in Drupal. They typically extend the BlockBase class and reside in a modules src/plugin/Block directory. 

Now, with that in mind, I hadn't done this sort of block load/rendering/creating for quite some time so I was kinda lost! I needed to load 2 views blocks and 1 webform block. After a little while of scratching my head, it occured to me that I need to look into the src/plugin/Block directory of the views/webform module to work what I needed. Initially I was struggling with the plugin_id value of the createInstance method on the plugin.manager.block service. 

Step 1

Looking at the plugin definition of each block type I was able to finally workout the ID needed to get an instance of each block. For views, it was views_block:<view_machine_name>-<view_display>. This was particularly tricky because the plugin definition doesn't actually tell you what the exact plugin id is. Sure, it gives you the first part e.g. views_block and for most plugin blocks that would have sufficed. But not with Views. During debugging, I noticed that I wasn't getting anything back from ->createInstance('views_block') so I had to go hunting for what wasn't working. Turns out the ID is made up in the pattern I outlined above. I found this via the deriver class within ViewsBlock (the plugin's class) and found these lines of code:

// Add a block plugin definition for each block display.
if (isset($display) && !empty($display->definition['uses_hook_block'])) {
  $delta = $view->id() . '-' . $display->display['id'];

It was then I knew the makeup of the plugin_id. I said it before, it's tricky and this was fairly well hidden. Nowhere is this documented! 

The webform ID is thankfully quite different, it was just webform_block but required making use of the second parameter of createInstance(): $configuration. This is an array of information/settings that you can configure manually in the blocks UI. In this case, webform wanted me to pass in a webform_id key with the ID of the webform I wanted in the block. Again I found this via debugging the class for the plugin Drupal\webform\Plugin\Block and saw the webform_id key inside the defaultConfiguration() method. 

You can already see the difficulty here: no two plugin block definitions are the same. Views was quite happy for me to pass in the view I wanted in the $plugin_id parameter, whereas webform wanted the webform I wanted to use to be passed into the createInstance() method via the $configuration parameter.

Step 2

Really easy step this, once you've sorted out your plugin_id and you're happy that you get something back that resembles what you would expect, you now need to call ->build() on your new block instance. Don't worry, a full example will be shown below. 

Step 3

Now that you've (hopefully) got a render array of your block, you can now add some of the configurations you would expect to see if you were placing a block in the UI (/admin/structure/block). E.g. you can set the block level config like the label (via #configuration), the ID of the block, attributes etc plus other block_plugin related configuration.

Step 4

Render your block, simples! Assign your block to the $variables array (if you're in a preprocess for example) and then print your block out in a twig template. Done :) 

<div>{{ my_plugin_block }}</div>

Code examples

// A webform block example
$configuration = [
  'label' => 'Contact us',
  'label_display' => BlockPluginInterface::BLOCK_LABEL_VISIBLE,
  'webform_id' => 'contact_us',
  'provider' => 'webform',
];
$contact_form_instance = $plugin_block_manager->createInstance('webform_block', $configuration);
$contact_form_render['content'] = $contact_form_instance->build();
$contact_form_render += [
  '#theme' => 'block',
  '#id' => 'block-webform-contact-us',
  '#attributes' => [],
  '#contextual_links' => [],
  '#configuration' => $configuration,
  '#plugin_id' => $contact_form_instance->getPluginId(),
  '#base_plugin_id' => $contact_form_instance->getBaseId(),
  '#derivative_plugin_id' => $contact_form_instance->getDerivativeId(),
];
$variables['contact_form_block'] = $contact_form_render;
// A view block example
$related_posts_block_instance = $plugin_block_manager->createInstance('views_block:blog-related_blog_posts');
$related_posts_block_instance['content'] = $related_posts_block_instance->build();
$related_posts_block_instance += [
  '#theme' => 'block',
  '#id' => 'block-blog-releated-blog-posts',
  '#attributes' => [],
  '#contextual_links' => [],
  '#configuration' => ['provider' => 'views_block'],
  '#plugin_id' => $related_posts_block_instance->getPluginId(),
  '#base_plugin_id' => $related_posts_block_instance->getBaseId(),
  '#derivative_plugin_id' => $related_posts_block_instance->getDerivativeId(),
];
$variables['related_posts_block'] = $related_posts_block_instance;

 

Tips and tricks

  1. If you're still strugging to work out what your plugin_id should be, you can run this handy drush command to get a list of definitions:
drush ev "print_r(array_keys(\Drupal::service('plugin.manager.block')->getDefinitions()))";

2. After a quick win? You can make use of the twig tweak module to produce a 1 liner to render out a plugin block. See this article on how to do it.

Gotchas

As with anything these days, not all is as it seems.... there are a few things you should be aware of when loading/rendering blocks like this: 

  1. Some hooks won't fire with plugin blocks that are loaded in the way outlined in this article. Blocks like (and not limited to) hook_block_view() and hook_block_build_alter() won't run for these types of blocks. And they're really common hooks, too! So just be mindful. If you're in a hook and it's not running when you expect it to (for a plugin block you've loaded) this is likely the reason why.
  2. Be careful with access. Some blocks may have their own access checks and this method of loading can bypass those - whenever loading blocks like this just double check you're correctly following - or reimplementing - the access that was originally intended with that blocks.
  3. Views blocks might lose the context that was originally intended for them. Contexts such as arguments etc may need to be re-set so the block can function properly.  
  4. The same goes for caching. You may need to re-implement the caching that was originally set for the block you've loaded. it should be as simple as using the following code snippet:
CacheableMetadata::createFromRenderArray($build)
  ->merge(CacheableMetadata::createFromRenderArray($content))
  ->applyTo($build);
Published in:

Hi, thanks for reading

ComputerMinds are the UK’s Drupal specialists with offices in Bristol and Coventry. We offer a range of Drupal services including Consultancy, Development, Training and Support. Whatever your Drupal problem, we can help.