To be honest, not sure of a good title for this. This is the issue:

I am using the TOC API to create a block. This API requires parsing the node content to determine the heading structure (H2s, H3s, etc). The module does not include a block for this as it is just an API module. I wrote a custom block to use the API to create this block. This works fine.

I now have a use case where I need to put multiple versions of this block on the page, but I am only able to render one of them. This has nothing to do with the TOC API but how the node content is rendered in the block code. I have simplified the code to remove all the API parts:

  public function build() {
    // Required to break block - render page - insert block - render page, etc, etc.
    $rendered = &drupal_static('toc_block_rendered');
    if (isset($rendered) && in_array($this->configuration['toc_type'], $rendered)) return [];
    else $rendered[] = $this->configuration['toc_type'];

    $build = [];

    // Get TOC Type configured for this Block.
    $toc_type_id = $this->configuration['toc_type'];

    $node = \Drupal::routeMatch()->getParameter('node');
    $view_builder = \Drupal::entityTypeManager()->getViewBuilder('node');
    $full_content = $view_builder->view($node, 'full');
    $content = (string) \Drupal::service('renderer')->render($full_content);

    return ['#markup' => $this->configuration['toc_type']];

$this->configuration['toc_type'] is a configuration value set for the block instance and is set differently for each of the 2 blocks I have placed on the page.

The static var code at the top is required to break the recursive loop caused due to trying to render the page which has the block which tries to render the page which has the block.. and so on and so on.

This simplified block just renders the block's config value. Although 2 of them are added to the node layout; only one gets shown. If I comment out the $view_builder->view($node, 'full') line; then both are displayed correctly.

I have tried multiple approaches such as static and even session vars for the rendered content; but with no luck. I also tried cloning or createDuplicate() of the node but this also does not help.

Can anyone explain why this is being broken by running this code for a 2nd instance of the block and if there is a better way to do this?

NOTE: the session var idea does work but the code I had for this isn't properly seeded the first time through. The 2nd refresh of the page shows both blocks. I would need a way to set for the first time and then also to clear these in a later hook (hook_node_view). But ugly approach.

  • What is correct? "the 2 blocks I have placed on the page" or "Although 2 of them are added to the node layout"
    – 4uk4
    Commented Aug 3, 2023 at 11:42
  • I think i understand what is going on; when the first block is added, the next time the full node render happens it clears that block. This is why only the last block is displayed. I have found the right way to do this (for my specific use case). Each TOC block is only intended for 1 of 4 Body fields added to the node. I can simply render that single field to use in each block and i suspect this will work. Testing now.
    – liquidcms
    Commented Aug 3, 2023 at 12:46

1 Answer 1


Did you consider a #post_render callback to avoid the early and recursive rendering?

If you want to place the block via UI in the node layout then return from the block builds static placeholders. For example:

return ['#markup' => '<span toc-data="' . $this->configuration['toc_type'] . '"></span>'];

Otherwise you could simply put the placeholders directly in the node build in the same hook where you add the callback.

The post render callback receives the entire HTML of the node layout which it can use for the TOC API and then replace the placeholders.

See How to alter page content?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.