Providing module-defined local tasks

Last updated on
24 March 2017

Local tasks are what generate the tabs on top of pages (up to two levels). These are mostly used on administrative pages but frontend pages like user pages or the registration/login/new password pages also use local tasks. There is a very simple YAML based declarative format to define local tasks in Drupal 8.

Define static local tasks

Most local tasks that you need to define will be static, and can therefore be provided in a file named after your module. Eg. if the module name is 'example', the file will be example.links.task.yml and placed in the root of the module.

example.admin: # The first plugin ID
  route_name: example.admin  
  title: 'Settings'
  base_route: example.admin
example.admin_3rd_party: # The second plugin ID
  route_name: example.admin_3rd_party
  title: 'Third party services'
  base_route: example.admin
  weight: 100

This file would define two tabs, one for the example.admin route and another for the example.admin_3rd_party route. It is advised that you define the plugin ID (the top level YAML key) the same way you name the route for consistency. If you need to reuse the same route name for different tabs (either because you need a subtab, see below, or you want to integrate the same route under a different tab), then append a sensible suffix to the route name to form the tab name. The title of the tab will show up on the user interface and the tab where the base_route is the same as the name of the route where the "default" tab appears. The base_route is used to group together related tabs. You can also provide weights for the tabs if needed with the weight key, which the tab whose route is the same as the base_route will by default get a negative weight and appear on the left.

To provide multiple levels of tabs, use the parent_id to relate a tab to its parent and use the same base_route to group the set of tabs together. Note that if parent_id is supplied, then the base_route value can and should be omitted, since it will be supplied from the parent local task.

For example, look at the local tasks defined by the block_content module:

entity.block_content.collection: # (1) Non-default tab by side of "Block layout" (which is on block.admin_display)
  title: 'Custom block library'
  route_name: entity.block_content.collection
  base_route: block.admin_display
block_content.list_sub: # (2) Default subtab, same route as the parent tab, so different tab ID. 
  title: Blocks
  route_name: entity.block_content.collection
  parent_id: entity.block_content.collection
entity.block_content_type.collection: # (3) Non-default subtab alongside the "Blocks" default tab
  title: Block types
  route_name: entity.block_content_type.collection
  parent_id: entity.block_content.collection
  weight: 1

entity.block_content_type.edit_form: # (4) Default edit tab on content block.
  title: 'Edit'
  route_name: entity.block_content_type.edit_form
  base_route: entity.block_content_type.edit_form
entity.block_content.delete_form: # (5) Non-default delete tab on content block.
  title: Delete
  route_name: entity.block_content.delete_form
  base_route: entity.block_content.canonical

Visually, the first three appear integrated in the block admin interface:

The last two appear when editing a content block:

Finally, you can provide string context for the tab title in a title_context key, so if the tab text is ambiguous (such as 'Extend', 'May', etc.) the string context helps translators pick the right translation. This is later passed on to t().

Dynamic local task generation

Sometimes a static list of local tasks is not enough. For example, Views, Content translation and Configuration translation add local tasks to a wide variety of pages in Drupal. To achieve this, add a local task with a derivative key pointing to a class that generates the dynamic local tasks. Your example.links.task.yml would look like the following:

  deriver: 'Drupal\example\Plugin\Derivative\DynamicLocalTasks'
  weight: 100

Generate the local tasks in the derivative class placed at src/Plugin/Derivative/DynamicLocalTasks.php based on the above reference:

 * @file
 * Contains \Drupal\example\Plugin\Derivative\DynamicLocalTasks.

namespace Drupal\example\Plugin\Derivative;

use Drupal\Component\Plugin\Derivative\DeriverBase;

 * Defines dynamic local tasks.
class DynamicLocalTasks extends DeriverBase {

   * {@inheritdoc}
  public function getDerivativeDefinitions($base_plugin_definition) {
    // Implement dynamic logic to provide values for the same keys as in example.links.task.yml.
    $this->derivatives['example.task_id'] = $base_plugin_definition;
    $this->derivatives['example.task_id']['title'] = "I'm a tab";
    $this->derivatives['example.task_id']['route_name'] = 'example.route';
    return $this->derivatives;


In this example, we don't do anything dynamic, but you would only use this code structure if you do need to generate dynamic local tasks.

See config_translation.links.task.yml and ConfigTranslationLocalTasks for a more involved example.

If you need to use parent_id with your dynamic tasks, be sure to prefix the parent_id with the deriver ID from *task.yml. For example:

  $this->derivatives['example.task_id']['parent_id'] = "example.local_tasks:example.task_id"

Customising local task behaviour

You can customise behaviour of your local tasks by extending LocalTaskDefault. For example, you can provide a dynamic title. You need to provide the class name of the custom local task implementation on the local task definition in the class key.

For example, see UserTrackerTab which is dependent on the current user ID in the path. It is defined with this tracker.links.task.yml entry (note the class key):

  route_name: tracker.users_recent_content
  title: 'My recent content'
  class: '\Drupal\tracker\Plugin\Menu\UserTrackerTab'

Another core example is ConfigTranslationLocalTask.

Altering local tasks

Use hook_menu_local_tasks_alter to alter existing local tasks.