Discourage $main_content['#title'] in favor of route titles and title callbacks

Created on 20 October 2014, almost 11 years ago
Updated 7 August 2025, 5 days ago

Problem/Motivation

In HEAD, we MUST render the main content FIRST, because we need that for the title. i.e. we can't just render $page, we must first render $page['content'], because $page['content']['#title'] may define the page title, which we need to print as part of $page's template.

This breaks the stack-based bubbling, because rendering $page['content'] (the main content) first separately means its bubbleable metadata (attachments, cache tags and #post_render_cache callbacks) won't be part of the stack of metadata being bubbled when rendering $page. The reason HEAD gets around this are: hacks that use globals to juggle things around, which we've been working to get rid of, plus drupal_render() not yet being as strict as it should be (a non-recursive (root) call to drupal_render() that does not result in an empty stack at the end of the root drupal_render() call does not complain currently, but should throw an exception, because it means bubbleable metadata is being left behind).
As of #9, this is no longer true. It'd still be better if we could enforce this, but we can't. But we can convert many (most?) uses in Drupal 8 HEAD to no longer use $main_content['#title']. Also see #7.

It also creates an utterly awful DX, the first testament of which is Drupal core itself: its *.routing.yml files are littered with _title attributes which are never ever used, because they're overridden by $main_content['#title'] ($page['content']['#title'])!
So: we think we define a title, but then dynamically override it. Or also: we think we override the title in a hook_form_alter() and then it's never actually overridden due to a change elsewhere, and is then falling back to the static _title. To top it off, the path-based breadcrumb builder will just look at every single path component along the way… including those that don't represent a valid path! E.g. /nl/node/1/translations/add/en/nl, for which _title = 'Add' is defined, but it's dynamically overridden using #title. The breadcrumb builder then gets the titles for /nl/node/1/translations/add/en and /nl/node/1/translations/add, which is 'Add' according to the static title, but in fact neither of those paths are *valid*! They both trigger exceptions. So we get a breadcrumb like Home > NODE TITLE > Translations > Add > Add, but neither of the two last links work, and this fact is subtly hidden, in part thanks to the combination of _title and #title.
An incredibly large percentage of titles is currently broken in this way in Drupal 8 HEAD.

I'd argue something as fundamental as page titles for a CMS should work in a very clear, consistent way, and bubbling is clearly not the best way to achieve that.

Proposed resolution

Remove the ability to set a dynamic page title using #title, always use _title or _title_callback.

Remaining tasks

Get green.

User interface changes

None. Except more consistent page titles for entity routes.

API changes

No longer able to set/override the page title using #title on main content returned in a _content or _form controller

πŸ“Œ Task
Status

Postponed

Version

11.0 πŸ”₯

Component

base system

Created by

πŸ‡§πŸ‡ͺBelgium wim leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

Live updates comments and jobs are added and updated live.
  • API change

    Changes an existing API or subsystem. Not backportable to earlier major versions, unless absolutely required to fix a critical bug.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024