Mapping Routes to Controllers in Drupal: The _controller Keyfor Drupal 8 , 9 , 10 , and 11

Last updated :  

You've now written the path, the requirements, and seen how parameters get upcast. The one piece left is the line that actually connects a route to real, executable code: _controller. This lesson looks closely at that one key — the different formats it can take, and exactly how Drupal resolves the string into a running method.

What you'll learn in this lesson

  • The ClassName::method string format and what each half means
  • Why Drupal can find your class without you registering it anywhere else
  • What your controller method is expected to return

The _controller string, dissected

page_example.simple:
  path: '/examples/page-example/simple'
  defaults:
    _controller: '\Drupal\page_example\Controller\PageExampleController::simple'
    _title: 'Simple - no arguments'
  requirements:
    _permission: 'access simple page'

The value is a single string in FullyQualifiedClassName::methodName format. Everything before the :: is the complete namespaced class name — here, \Drupal\page_example\Controller\PageExampleController — and everything after it is the public method Drupal should call.

Why Drupal can find the class without extra registration

Drupal follows the PSR-4 autoloading standard: a class's namespace directly mirrors its file location on disk. \Drupal\page_example\Controller\PageExampleController maps to the file src/Controller/PageExampleController.php inside the page_example module. Because the namespace and file path always correspond exactly, Drupal's autoloader locates and loads the file automatically the moment the class is referenced — there's no manual require statement and no separate class registry to update.

What the method needs to return

public function simple() {
  return [
    '#markup' => $this->t('Simple page: The quick brown fox jumps over the lazy dog.'),
  ];
}

A controller method must return a render array — never a raw HTML string directly. Drupal takes that array and runs it through the full theming and rendering pipeline (wrapping it in the active theme, adding blocks, applying any relevant alter hooks) before the final HTML reaches the browser. You'll go deeper into what a render array can contain in the next lesson.

A shorthand alternative: _form and _entity_view

Not every route needs a full custom controller class. If a route's entire job is to render a form, you can skip _controller entirely and write _form: '\Drupal\my_module\Form\MyForm' instead — Drupal handles instantiating and rendering the form for you. Similarly, _entity_view and _entity_list delegate to an entity type's registered handlers. These are worth knowing about, even though this lesson focuses on the general-purpose _controller key.

Gotcha: the leading backslash in _controller: '\Drupal\page_example\Controller\PageExampleController::simple' matters — it's an absolute, fully-qualified namespace reference. Omit it and, depending on context, PHP may try to resolve the class name relative to the wrong namespace, causing a "class not found" error that has nothing to do with your actual code being broken.

Quick check: your controller method returns the plain string "Hello" instead of an array. What happens when the route is visited? Drupal's render pipeline expects a render array, so this throws a fatal error rather than silently displaying "Hello" — always return an array, even for the simplest possible output like ['#markup' => 'Hello'].

Key takeaways

  • _controller is a string in FullyQualifiedClassName::methodName format connecting a route to real PHP code.
  • PSR-4 autoloading means Drupal finds your controller class automatically from its namespace — no manual registration required.
  • A controller method must always return a render array, never a raw string — the render pipeline expects that shape unconditionally.
  • _form, _entity_view, and _entity_list are shorthand alternatives to _controller for routes that just render a form or an entity's existing display handlers.

Coming up next

Time to actually build one of these controller classes from scratch. The next lesson, Creating a Controller Class, walks through extending ControllerBase and writing a real, working method.