Render API
In Drupal, pages, blocks and forms are built as PHP data first and turned into HTML last, so themes can change the markup and caches can reuse it.
The Render API is the part of Drupal core that turns structured data into HTML. Modules return render arrays, nested PHP arrays whose properties such as #type, #theme, #markup and #cache say what to show and how, instead of writing markup. The renderer walks the array, calls the theme system and applies caching.
How the Render API works in Drupal 11
In a render array, keys that start with # are properties and all other keys are children, which are render arrays themselves. Core documents the design goal: module code returns structured data so a theme can control the markup.
The main properties are:
#typepicks a render element. Elements are plugins; the textfield, for example, is a class with the#[FormElement('textfield')]attribute in theElementplugin namespace.#themepicks a theme hook, defined withhook_theme()and implemented by a template, normally Twig.#markupand#plain_texthold simple output.#markupis filtered withXss::filterAdmin(), which removesscriptandstyletags.#cacheholdskeys,contexts,tagsandmax-age. Contexts, tags and max-age bubble up to the parent arrays.#attachedadds libraries,drupalSettingsand other page-level data.#pre_render,#post_renderand#lazy_builderhold callbacks.
In 11.2, Renderer::doRender() handles one element in this order: it checks #access, returns a cached copy if #cache has keys and a copy exists, handles a #lazy_builder if there is one, either at once or through a placeholder, then runs the #pre_render callbacks. It filters #markup, renders the children (through #theme or by recursion), applies #theme_wrappers, runs #post_render callbacks and adds #prefix and #suffix.
Placeholdering keeps pages cacheable. By default, renderer.config treats a fragment with max-age 0, or one that varies by session or user, as a candidate for a placeholder that is rendered last.
Example
A controller method returns a render array, and Drupal does the rest:
$build['intro'] = [
'#markup' => '<p>Welcome back.</p>',
];
$build['links'] = [
'#theme' => 'item_list',
'#items' => ['First', 'Second'],
'#cache' => [
'tags' => $node->getCacheTags(),
'contexts' => ['languages'],
],
];
$build['#attached']['library'][] = 'my_module/slider';
return $build;
The cache tags make Drupal drop the cached list when the node changes. The my_module/slider library is a placeholder name for an asset library the module would define.
Common mistakes
- Calling
render()outside a render context. Drupal throws aLogicExceptionthat tells you to userenderRoot(),renderInIsolation()or a#lazy_builderor#pre_rendercallback instead. The olderrenderPlain()is deprecated since Drupal 10.3. - Untrusted callbacks. Render callbacks must be anonymous functions or methods of a class that implements
TrustedCallbackInterfaceorRenderCallbackInterface, a rule introduced in Drupal 8.8.0. In Drupal 11 any other callback throws anUntrustedCallbackException. - Expecting script tags in
#markup. The admin tag filter removes them. Attach a library, or use a theme hook. - Missing cache metadata. If an array depends on data that can change or on the user's language or role, declare the tags and contexts. The metadata is what tells Drupal when to vary or invalidate the cached copy.
- Complex arguments to a lazy builder. Drupal's assertions require scalar values or
NULLin the argument list, and a lazy builder element cannot have children.
For marketing and content teams
You rarely see render arrays, but you see their effects. Because modules hand structured data to the theme, one change in the theme can restyle a list or a form everywhere it appears. Because the output is cached with tags, Drupal knows which cached pieces to drop when you save a node. If a change does not appear on a page, ask the developer whether the component declares cache tags for the data it displays.
Sources
- Drupal API: Render API overview (theme.api.php, 11.2.0)
- Renderer, Drupal 11.2.0 source
- Drupal.org: Render arrays
- Drupal.org: Auto-placeholdering
- Drupal change record: Render callbacks must be a closure or implement TrustedCallbackInterface or RenderCallbackInterface
- core.services.yml, Drupal 11.2.0 source (renderer.config)