Adding action links in .links.action.ymlfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Visit a content list in Drupal and you'll usually see a button above the table — "Add content," "Add contact," something that starts a new one of whatever the list is showing. That button isn't hand-placed in a template; it's an action link, declared once in .links.action.yml and told exactly which pages it's allowed to appear on. This lesson covers how that works.

What you'll learn in this lesson

  • What makes an action link different from a menu link or a local task
  • The appears_on key — deciding exactly which pages show the button
  • Why an action link's target route usually needs its own access check

The source file

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

content_entity_example.contact_add:

  # The route which will be invoked when the link is clicked.
  route_name: content_entity_example.contact_add
  title: 'Add contact'

  # The pages where the link appears, indicated by their route names.
  appears_on:
    - entity.content_entity_example_contact.collection
    - entity.content_entity_example_contact.canonical

How it works, key by key

The top-level key, route_name, and title

Same shape as a menu link: the top-level key (content_entity_example.contact_add) is this action link's own unique machine name, route_name is which route the button actually navigates to when clicked, and title is the visible button label — here, "Add contact."

appears_on — the key unique to action links

appears_on:
  - entity.content_entity_example_contact.collection
  - entity.content_entity_example_contact.canonical

This is the whole point of an action link: rather than being placed by a template or a block, it declares its own list of route names it's allowed to render on. Here, the "Add contact" button shows up in exactly two places — the contact collection (list) page, and any individual contact's own canonical view page — and nowhere else on the entire site. Add a third route name to this list, and the button starts appearing there too, with zero template changes.

Why the target route still needs its own access check

Notice this file says nothing at all about permissions. An action link controls where the button is drawn, not who's allowed to click it — those are two separate concerns. The content_entity_example.contact_add route itself is the thing responsible for enforcing access (typically via _entity_create_access or a permission requirement in its own *.routing.yml entry). Get this backwards — relying on the action link alone — and a user without permission would still be able to reach the form directly by typing its URL, since the route was never actually locked down.

A subtle trap: if you forget the target route's own access requirement, the action link won't even hide itself from unauthorized users first — the button will happily render for everyone appears_on allows, and only the route's (missing) access check was ever going to stop someone from actually submitting the form. Always secure the destination route independently of where its button happens to show up.

See it for yourself

Visit /content_entity_example_contact on your own DDEV site (the contact collection page) and look for the "Add contact" button above the list — that's this exact YAML entry, rendered because this page's route name is the first one listed under appears_on.

Quick check: you want "Add contact" to also appear on your site's front page. Is adding the front page's route name to appears_on enough by itself, with no other changes? Yes for visibility — the button will render there. But if the front page is reachable by anonymous visitors and the underlying add-contact route isn't properly access-restricted, you've just made an unprotected "create content" button visible to anyone, which is exactly the trap the note above describes.

Key takeaways

  • Action links are for "create a new one of this" buttons — appears_on is the key that scopes exactly which route(s) the button renders on.
  • An action link only controls where a button is drawn; it never enforces permissions by itself — the destination route must have its own access check.
  • Adding a route name to appears_on is the entire mechanism for making a button show up somewhere new — no template or controller changes needed.

Coming up next

You've now seen all three static link types — menu links, local tasks, and action links — each declared once in YAML and each answering a different navigation question. The next lesson covers what happens when static YAML isn't flexible enough: altering menu links programmatically at runtime with hook_menu_links_discovered_alter().