How can I change the active Drupal 8 theme programmatically?

In Drupal 6, we used the following code.

global $custom_theme;
$custom_theme = 'garland';

In Drupal 7, we used hook_custom_theme().

In Drupal 8, what's the correct way to do this?


In Drupal 8, you use theme negotiators, which essentially are services using a specific tag. See the theme negotiators implemented by Drupal, to understand exactly how they work; the example given in the change record is not updated.


    class: Drupal\user\Theme\AdminNegotiator
    arguments: ['@current_user', '@config.factory', '@entity.manager', '@router.admin_context']
      - { name: theme_negotiator, priority: -40 }


namespace Drupal\user\Theme;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\AdminContext;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;

 * Sets the active theme on admin pages.
class AdminNegotiator implements ThemeNegotiatorInterface {

   * The current user.
   * @var \Drupal\Core\Session\AccountInterface
  protected $user;

   * The config factory.
   * @var \Drupal\Core\Config\ConfigFactoryInterface
  protected $configFactory;

   * The entity manager.
   * @var \Drupal\Core\Entity\EntityManagerInterface
  protected $entityManager;

   * The route admin context to determine whether a route is an admin one.
   * @var \Drupal\Core\Routing\AdminContext
  protected $adminContext;

   * Creates a new AdminNegotiator instance.
   * @param \Drupal\Core\Session\AccountInterface $user
   *   The current user.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
   *   The entity manager.
   * @param \Drupal\Core\Routing\AdminContext $admin_context
   *   The route admin context to determine whether the route is an admin one.
  public function __construct(AccountInterface $user, ConfigFactoryInterface $config_factory, EntityManagerInterface $entity_manager, AdminContext $admin_context) {
    $this->user = $user;
    $this->configFactory = $config_factory;
    $this->entityManager = $entity_manager;
    $this->adminContext = $admin_context;

   * {@inheritdoc}
  public function applies(RouteMatchInterface $route_match) {
    return ($this->entityManager->hasHandler('user_role', 'storage') && $this->user->hasPermission('view the administration theme') && $this->adminContext->isAdminRoute($route_match->getRouteObject()));

   * {@inheritdoc}
  public function determineActiveTheme(RouteMatchInterface $route_match) {
    return $this->configFactory->get('system.theme')->get('admin');


The code is pretty easy to understand: The applies() method returns TRUE when the current route is the one for which your module wants to change the theme; the determineActiveTheme() method returns the theme machine name of the theme to apply.

See also ThemeNegotiator::determineActiveTheme() should not require a RouteMatch to be passed in for a possible change on the arguments received from the methods used by theme negotiators; if that patch is applied, you will need to change your theme negotiator code too.

  • Shouldn't applies() be written as applies($route_match) in the above example? Posted the same question to the linked d.o. page. Thanks! – Stefanos Petrakis May 22 '16 at 15:41
  • @StefanosPetrakis Hmmm... Any current implementation does get that as parameter, contrary to what that change record says. – kiamlaluno May 22 '16 at 17:04
  • I have updated the answer, using the code Drupal core is actually using in one of its theme negotiators. – kiamlaluno May 22 '16 at 17:17

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.