Custom node routes

Cycle path flyover over roads into Auckland
Sometimes it can be handy to have extra pages for a node (or any entity). For example:

  • To show different sets of information on separate pages for a single product, page, or thing.
  • So you can set different access requirements on each page for a node.
  • You want to block access to the ordinary route (e.g. node/123 and its aliased equivalent) for some reason, but you still want some other page to represent that node.

I’ve found a few people with that need on Drupal slack before, so I thought I’d write a guide because it’s surprisingly easy!

My imaginary requirement

I’m going to imagine my client has asked me to set up an additional page for products, to show information relevant to someone that has already bought the product. We’ll call it the ‘Product support’ page. This page could be public, but shown as a separate tab for products, or even just hidden as a link only sent out to customers that have ordered the specific product. Access could even be restricted to those people entirely. All the content to show on the page would come from fields on the product. We don’t really want to go creating another separate entity to hold data that’s only relevant to the product in question anyway. This makes sense from a data point of view as all the fields are for one thing, whether for existing or potential customers. (Of course, Field Group could be used to split those things into tabs on the product’s edit form, or a similar technique to this article’s could be used to separate them out into an additional edit page!)

First, set up a view mode for your node/entity

Just like teasers can be configured separately to the full content version of a node, you can create your own view mode. Add it from /admin/structure/display-modes/view on your site, and then enable it from the ‘Custom display settings’ section at the bottom of the ‘Manage display’ tab for your entity type/bundle. Once that’s done, navigate to the sub-tab for your view mode and choose the fields you’ll want to show.

So I’ll make one called ‘Product support’, and configure only fields like ‘Support helpline’, ‘Manuals’ and ‘Returns information’ to show in this view mode. I will probably go hide those fields in the usual (default/full) mode too.

Next, set up the page to use that view mode

HTML page routes for viewing entities are usually set up by the ‘route_provider’ specified in an entity type’s annotation block. For nodes, that’s DrupalnodeEntityNodeRouteProvider, which dynamically sets up the view, edit & delete routes (take a look!). But you can define your route with some simple YAML. In a custom module, add (or use an existing) mymodule.routing.yml file, with the following code. Replace ‘mymodule’ with your module’s machine name, and ‘product_support’ with the machine name of your view mode:

  path: '/node/{node}/product-support'
    _entity_view: 'node.product_support'
    _title: 'Product support'
    node: 'd+'
    _entity_access: 'node.view'
    _custom_access: 'mymodule_product_support_access'

The last line contains the name of a function you might want to use to add any custom access logic. The function could just be in your .module file. In my case, I might look up the current user’s orders to check whether they have bought the product before allowing access. You might want to at least restrict which bundles the route works for, like so:

 * Extra access callback for product pages.
function mymodule_product_support_access(DrupalCoreEntityContentEntityInterface $node) {
  return DrupalCoreAccessAccessResult::allowedIf($node->bundle() === 'product');

But you can just omit that _custom_access: line from the YAML entirely to just use the same access that the ordinary node page has, no problem.

Rebuild the site caches; you’re done!

Now you can access /node/123/product-support to view a different page of content for your product! If you want to set it up as a tab where the usual ‘View’/’Edit’ tabs of a node would be, then use the following YAML in a mymodule.links.task.yml file in your module. Again, replace ‘mymodule’ and ‘product_support’ as appropriate, and then rebuild the site caches to see it work:

  route_name: mymodule.product_support
  title: 'Product support'
  base_route: entity.node.canonical
  weight: 5

All of this can work for any entity type – try just replacing ‘node’ in each of these code snippets with the machine name of the entity type that you want to use.

There’s a module for that

There are modules like View Mode Page which let you set these extra pages up in the UI. But I’ve found my additional pages usually need some additional bespoke logic though, whether for access or something else on the page. So given how little actual code is needed, I tend to just make them this way. But it does have some handy features, like supporting URL aliases for nicer paths.

Entity Form Mode also claims to help you make separate edit pages for form modes, which is very handy. But again, I find these tend to have even more interesting bespoke requirements with custom access logic. But see how you get on. Making these in custom code can be just as easy anyway – just replace the _entity_view: 'node.product_support' part of the routing YAML with _entity_form: 'node.my_form_mode', and the _entity_access part should use ‘node.update’ instead of ‘node.view’.

I’m sure there are some interesting use cases for this – let me know in the comments what you’ve needed custom entity routes for!

Photo by Thant Zin Oo on Unsplash

Go to Source