Defining local tasks in .links.task.ymlfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Visit an article's edit page and you'll see tabs across the top — "View," "Edit," "Delete," maybe "Revisions." Those aren't menu links buried in a sidebar; they're local tasks, a different navigation mechanism purpose-built for "here are the other things you can do with the exact thing you're already looking at." This lesson covers .links.task.yml, the file that defines them.

What you'll learn in this lesson

  • How local tasks differ from ordinary menu links, and when to reach for each
  • base_route — the key that groups a set of tabs together
  • How to build two levels of tabs (primary and secondary) on the same page
  • Why one tab in a group gets a negative weight by default

The source file

Path (relative to the Examples module's root): modules/menu_example/menu_example.links.task.yml

# This file creates static local tasks (Tabs).
# This file will be needed to place in module root.
#
# 'title' of the tab will show up on the user interface and tab.
# '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.
# 'weight' is used to provide weights for the tabs if needed.
# The tab whose route is the same as the base_route will by default
# get a negative weight and appear on the left.
# 'parent_id' is used to create multi level of tabs.
# To relate a tab to its parent use same name as parent_id as shown below in
# examples.menu_example.tabs.secondary.
#
examples.menu_example.tabs:
  route_name: examples.menu_example.tabs
  title: Default primary tab
  base_route: examples.menu_example.tabs

examples.menu_example.tabs_second:
  route_name: examples.menu_example.tabs_second
  title: Second
  base_route: examples.menu_example.tabs
  weight: 2

examples.menu_example.tabs.secondary:
  route_name: examples.menu_example.tabs
  title: Default secondary tab
  parent_id: examples.menu_example.tabs

examples.menu_example.tabs_default_second:
  route_name: examples.menu_example.tabs_default_second
  title: Second
  parent_id: examples.menu_example.tabs

How it works, key by key

base_route — the key that ties tabs together into one group

Every local task that should appear as a tab on the same page shares the same base_route value. It doesn't have to be the route this specific tab links to — examples.menu_example.tabs_second has its own distinct route_name, but shares the group's base_route so it renders as a sibling tab alongside the default one.

The "default" tab gets a negative weight automatically

Notice examples.menu_example.tabs has a route_name that's identical to its own base_route. Drupal recognizes this pattern and treats it as the group's default tab, automatically pulling it to the leftmost position — you don't need to set weight on it yourself for that positioning to happen, though you still can to fine-tune order among the rest.

weight among non-default tabs

examples.menu_example.tabs_second:
  route_name: examples.menu_example.tabs_second
  title: Second
  base_route: examples.menu_example.tabs
  weight: 2

Same ordering rule as ordinary menu links: lower weight sorts first. Since the default tab already claims the leftmost spot implicitly, weight here really just controls order among everything after it.

parent_id — a second row of tabs

examples.menu_example.tabs.secondary:
  route_name: examples.menu_example.tabs
  title: Default secondary tab
  parent_id: examples.menu_example.tabs

This is the mechanism behind pages that show two rows of tabs — think a node's primary tabs (View/Edit/Delete) with a secondary row underneath for something like translations. Setting parent_id to another local task's own machine name nests it as a secondary tab beneath that primary one, using the exact same "route matching parent equals default" trick to decide which secondary tab is selected by default.

Local tasks vs. menu links — pick based on the question you're answering: a menu link answers "where can a visitor go from anywhere on the site?" A local task answers "what else can I do with the specific thing I'm looking at right now?" If you find yourself wanting a link that only makes sense in the context of the current page, that's a local task, not a menu link.

See it for yourself

Visit /examples/menu-example/tabs on your own DDEV site. You'll see a row of primary tabs (Default primary tab / Second / Third / Fourth) and, underneath, a second row of secondary tabs — both generated entirely from this one YAML file, no controller logic involved in deciding which tabs to show.

Quick check: you want to add a third row of tabs nested even deeper than secondary. Does .links.task.yml support that? No — Drupal's local task system only supports two levels, primary and secondary. A third level needs a different UI pattern entirely (commonly a set of links rendered inside the page content itself), not a deeper parent_id chain.

Key takeaways

  • Local tasks (tabs) answer "what else can I do with this specific page," while menu links answer "where can I go from anywhere" — choose based on that distinction, not habit.
  • base_route groups tabs together; the tab whose own route_name matches the group's base_route becomes the default, leftmost tab automatically.
  • parent_id creates a second, secondary row of tabs nested under a specific primary tab — Drupal core only supports these two levels.
  • weight orders tabs within their group exactly like it orders sibling menu links — lower sorts first.

Coming up next

Tabs cover "what else can I do here" for viewing content. The next lesson covers a third navigation mechanism aimed specifically at actions — the "Add contact," "Add content" style buttons that show up above a list, defined in .links.action.yml.