For a certain content type, if you want most users to be able to view the content type (node bundle) when it's loaded on landing pages or on other node pages, but you don't want them to be able to access the route entity.node.canonical for the content type, unless they have an explicit permissions (which would be given to admin role and editor role), what is the best way to do this in Drupal 8?

As an example, let's say a site has a "video" content type, when it's loaded in a block, such as a related videos block, you want everyone to be able to view it, but if a user tries to go to that video's canonical page, they get a 404, unless they have the role with the permission to view it.

This would be for certain bundles of content or media entities that I want to only allow access to in a block, but the canonical page should 404 if the user doesn't have the correct permission.

What is the best strategy to set this up in Drupal 8?

  • 1
    The answers are a good way for doing this yourself. Which sometimes is useful, especially if your conditions or logic what should happen is more complicated. But for what you need, drupal.org/project/rabbit_hole should work just fine – Berdir Aug 11 '17 at 18:56
  • Cool, I will check it out. – oknate Aug 12 '17 at 10:52

In Drupal 7, I believe you could do this with Rules. The Rules module is technically available for Drupal 8, but I couldn't figure out how to use it, so I'll show you how to wire up a custom module that redirects manually based on whatever your criteria is.

The basic idea is, in order to do custom redirects, you have to register an EventSubscriber service, subscribe to REQUEST KernelEvent, and then you have access to the entire request object.

In my company's case, I wrote a custom module called "redirect", so that's what this example will use.

First, create a redirect.services.yml file.

    class: Drupal\redirect\EventSubscriber\RedirectSubscriber
      - { name: event_subscriber}

Then, create the class referenced above (Drupal\redirect\EventSubscriber\RedirectSubscriber) in src/EventSubscriber/RedirectSubscriber.php.


namespace Drupal\redirect\EventSubscriber;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Drupal\node\Entity\Node;

class RedirectSubscriber implements EventSubscriberInterface {

  public function redirect(GetResponseEvent $event) {
    $request = $event->getRequest();
    $baseUrl = $request->getBaseUrl();
    $attr = $request->attributes;
    $roles = \Drupal::currentUser()->getRoles();

    if ($attr !== null) {
      $node = $attr->get('node');
      if ($node !== null && $node instanceof Node) {
        $type = $node->get('type')->getString();
        if ($type == 'seel_lesson_plan' && !in_array('administrator', $roles)) {
            new RedirectResponse("$baseUrl/url-to-404-page")

  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = ['redirect'];
    return $events;


To get a better idea of how this works, read an explanation of registering event subscribers, the official Events API page, and this other Drupal SE question about redirecting content pages.

  • 1
    Thanks for this, this looks close to what I need. Although I want to return a 404, not a 302 response. – oknate Aug 11 '17 at 16:33

I marked Lincoln's answer as correct as it got me 90% there. But here's my version, that throws a 404, instead of redirecting.

Add this to your mymodule.services.yml file:

    class: Drupal\mymodule\EventSubscriber\KernalEventSubscriber
      - { name: event_subscriber}

and then in /modules/custom/mymodule/src/EventSubscriber/KernalEventSubscriber.php

Here is the class.

 * Class KernalEventSubscriber
 * Restrict access to the canonical pages for certain entities
 * and node bundles.
class KernalEventSubscriber implements EventSubscriberInterface {

  public function checkAccess(GetResponseEvent $event) {

    // Routes to block, regardless of bundle, if
    // user lacks the appropriate role.
    $blocked_routes = [

    $allowed_roles = [

    $current_route = \Drupal::routeMatch()->getRouteName();

    $current_user_roles = \Drupal::currentUser()->getRoles();

    if (in_array($current_route, $blocked_routes)) {
      if (empty(array_intersect($allowed_roles, $current_user_roles))) {
        throw new NotFoundHttpException();

    // Node bundles to block, if
    // user lacks the appropriate role.
    $node_bundles = [

    if ($current_route == 'entity.node.canonical') {
      $node = \Drupal::routeMatch()->getParameter('node');
      if (in_array($node->bundle(), $node_bundles) && empty(array_intersect($allowed_roles, $current_user_roles))) {
        throw new NotFoundHttpException();


  static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = ['checkAccess'];
    return $events;


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.