Controlling Block Visibility with a Custom Field in Drupal 8 (updated for 9)

Awhile back I wrote up a pattern for creating static blocks on Drupal 8 sites. This week I was working on a site where one of those blocks needs to be enabled or disabled on specific nodes at the discretion of the content author. To make this happen, I’m adding a new feature to my pattern.

In older versions of Drupal there were a number of ways to go, like the PHP Filter, or custom handling in the block’s view hook, but I figured there were probably more appropriate tools for this in Drupal 8.  And I found what I needed in the Condition Plugin (more evidence that plugins are addictive). According to the change record they were designed to centralize a lot of the common logic used for controlling blocks, and I found it works quite nicely in this case as well (although a more generalized version might be useful).

I have the complete condition plugin at the end so you don’t have to get all the details exactly right as we go.

I started by adding a boolean field to the content type named field_enable_sidebar. Then I using drupal console generated the stub condition plugin:drupal generate:plugin:condition. In doing this the first time I also looked at the one defined in the core Node module to handle block visibility by content type.

The console will ask you a couple questions, obviously you can attach it to any module you’d like and call it whatever you’d like. For this example I have it in a fake module called my_blocks and the condition is named SidebarCondition. But the next couple questions are less obvious and more important.

Context Type should be entity

The context type should be set to entity since we are looking to work based on the node being displayed.

Context Entity Type should be Content

Next it’s trying to filter between entity types, and since we’re doing this based on the node content entity type, select “Content” to get a list of content entities on your site.

Context Definition ID should be Content

Finally select “Content” again since that’s the label for node entities in Drupal 8. If you have your field on another content entity type (like a taxonomy term, or a file), pick that entity here instead and rest of this should still work with minor editing.

Once you run through the wizard you’ll have a new file in your module: my_blocks/src/Plugin/Condition/sidebarContition.php

The condition plugin contains two main elements: a form that’s attached to all block settings forms, and an evaluate function that is called by blocks to determine if this condition applies in their current context.

buildConfigurationForm() defines the form array elements you need. In this case that means a simple checkbox to indicate that this condition applies to this block. We also need to define submitConfiguration() to save the values on block save.

public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['sidebarActive'] = [
'#type'          => 'checkbox',
'#title'         => $this->t('When Sidebar Field Active'),
'#default_value' => $this->configuration['sidebarActive'],
'#description'   => $this->t('Enable this block when the sidebar field on the node is active.'),
];
return parent::buildConfigurationForm($form, $form_state);
}

public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['sidebarActive'] = $form_state->getValue('sidebarActive');
parent::submitConfigurationForm($form, $form_state);
}

In the complete example you’ll see there is summary() which provides the human friendly description of the values that have been set for this condition.

Now let’s jump back to the top of the plugin and review the annotation. Conditions are annotated plugins and those questions I guided you through above were used to generate the annotation at the start of the file:

/**
* Provides the 'Sidebar condition' condition.
*
* @Condition(
*   id = "sidebar_condition",
*   label = @Translation("Sidebar block condition"),
*   context_definitions = {
*     "node" = @ContextDefinition(
*        "entity:node",
*        required = TRUE ,
*        label = @Translation("node")
*     )
*   }
* )
*/

This is defining the context you’ll want passed to the condition for evaluation. In this case we are requiring that a node entity labeled “node” is provided when we need it.

The real work of the plugin is handled by evaluate():

/**
* Evaluates the condition and returns TRUE or FALSE accordingly.
*
* @return bool
*   TRUE if the condition has been met, FALSE otherwise.
*/
public function evaluate() {
if (empty($this->configuration['sidebarActive']) && !$this->isNegated()) {
return TRUE;
}
$node = $this->getContextValue('node');
if ($node->hasField('field_enable_sidebar')&& $node->field_enable_sidebar->value) {
return TRUE;
}
return FALSE;
}

The first conditional ensures that this plugin doesn’t disable all blocks that aren’t using it. Next we ask for the context node value we defined in the annotation, which provides us the current node able to be displayed. Since not all node types are guaranteed to have our sidebar field we first check that it exists and then check its value and return the correct status for block display.

Now every time a user checks a box on the node, any blocks with this condition enabled will be displayed along with the node. And the best part is that the user doesn’t need to even have block display permissions, we’ve allowed them to bypass that part of the system entirely.

5 thoughts on “Controlling Block Visibility with a Custom Field in Drupal 8 (updated for 9)”

  1. I tried this and it works but when I uncheck this custom visibility’s checkbox and do a config export, the visibility declaration seems to still exist in the block’s YML file unlike the other visibility groups like Content Type, User Role and etc.

  2. just replicating my comment from the slack:

    to extend block visibility possibilities you can try this patch:

    https://www.drupal.org/project/drupal/issues/923934#comment-12401360

    it introduces the AND / OR conjunction operator for the visibility conditions so you can say oh I want this block visible if it is of my_content_type OR user has admin role OR the url starts with my/path/to/* etc..

    also if you use the Drupal Commerce module you can try the above by installing the https://www.drupal.org/project/commerce_quick_purchase module note that being installed the module exposes this functionality for all blocks on the system even if you don’t use the block provided by the module see more

    https://github.com/drugan/commerce_quick_purchase#block-visibility

  3. I came across this post when learning more about Condition plugins. It gives a really good example of the type of thing that can be done. You stated you found your starting point in this post on Stackoverflow.
    https://drupal.stackexchange.com/questions/203308/how-can-i-control-block-visibility-with-code

    “`For 8.2.x you need to create a Condition Plugin and implement the ContextProviderInterface.

    See an example from D.O. but it is somehow outdated (because BlockEvent and BlockSubscriberBase removed in favor of a context repository service).

    It would nice also to use Drupal console to generate the plugin:
    drupal generate:plugin:condition“`

    The part that is missing from the post if information related to the ContextProvidedInterface. I assume that’s how the block actually knows when it should or shouldn’t show based on the conditions, since I see no actual block-related code in the code you have provided.

    1. There is no block code because this creates a condition you can use with any block on your site. Once your plugin is ready, when you go to the block configuration form you should see a new condition you can add that works with this condition. I’ve used only slightly modified versions of this code on a variety of projects from 8.3 – 8.5 at this point.

  4. Good, very useful, it works.
    The only thing that is global as you said for all blocks and not specific as i wanted, anyway i used like that.
    Thanks for sharing!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.