Render API overview in Drupal 10
Same name and namespace in other branches
- 8 core/lib/Drupal/Core/Render/theme.api.php \theme_render
- 9 core/lib/Drupal/Core/Render/theme.api.php \theme_render
Overview of the Theme system and Render API.
The main purpose of Drupal's Theme system is to give themes complete control over the appearance of the site, which includes the markup returned from HTTP requests and the CSS files used to style that markup. In order to ensure that a theme can completely customize the markup, module developers should avoid directly writing HTML markup for pages, blocks, and other user-visible output in their modules, and instead return structured "render arrays" (see Render arrays below). Doing this also increases usability, by ensuring that the markup used for similar functionality on different areas of the site is the same, which gives users fewer user interface patterns to learn.
For further information on the Theme and Render APIs, see:
- https://www.drupal.org/docs/8/theming
- https://www.drupal.org/developing/api/8/render
- Theme system overview.
Render arrays
The core structure of the Render API is the render array, which is a hierarchical associative array containing data to be rendered and properties describing how the data should be rendered. A render array that is returned by a function to specify markup to be sent to the web browser or other services will eventually be rendered by a call to \Drupal\Core\Render\RendererInterface::render(), which will recurse through the render array hierarchy if appropriate, making calls into the theme system to do the actual rendering. If a function or method actually needs to return rendered output rather than a render array, the best practice would be to create a render array, render it by calling \Drupal\Core\Render\RendererInterface::render(), and return that result, rather than writing the markup directly. See the documentation of \Drupal\Core\Render\RendererInterface::render() for more details of the rendering process.
Each level in the hierarchy of a render array (including the outermost array) has one or more array elements. Array elements whose names start with '#' are known as "properties", and the array elements with other names are "children" (constituting the next level of the hierarchy); the names of children are flexible, while property names are specific to the Render API and the particular type of data being rendered. A special case of render arrays is a form array, which specifies the form elements for an HTML form; see the Form generation topic for more information on forms.
Render arrays (at any level of the hierarchy) will usually have one of the following properties defined:
- #type: Specifies that the array contains data and options for a particular type of "render element" (for example, 'form', for an HTML form; 'textfield', 'submit', for HTML form element types; 'table', for a table with rows, columns, and headers). See Render elements below for more on render element types.
- #theme: Specifies that the array contains data to be themed by a particular theme hook. Modules define theme hooks by implementing hook_theme(), which specifies the input "variables" used to provide data and options; if a hook_theme() implementation specifies variable 'foo', then in a render array, you would provide this data using property '#foo'. Modules implementing hook_theme() also need to provide a default implementation for each of their theme hooks, normally in a Twig file. For more information and to discover available theme hooks, see the documentation of hook_theme() and the Default theme implementations topic.
- #markup: Specifies that the array provides HTML markup directly. Unless the markup is very simple, such as an explanation in a paragraph tag, it is normally preferable to use #theme or #type instead, so that the theme can customize the markup. Note that the value is passed through \Drupal\Component\Utility\Xss::filterAdmin(), which strips known XSS vectors while allowing a permissive list of HTML tags that are not XSS vectors. (For example, <script> and <style> are not allowed.) See \Drupal\Component\Utility\Xss::$adminTags for the list of allowed tags. If your markup needs any of the tags not in this list, then you can implement a theme hook and/or an asset library. Alternatively, you can use the key #allowed_tags to alter which tags are filtered.
- #plain_text: Specifies that the array provides text that needs to be escaped. This value takes precedence over #markup.
- #allowed_tags: If #markup is supplied, this can be used to change which tags are allowed in the markup. The value is an array of tags that Xss::filter() would accept. If #plain_text is set, this value is ignored.
Usage example:
$output['admin_filtered_string'] = [
'#markup' => '<em>This is filtered using the admin tag list</em>',
];
$output['filtered_string'] = [
'#markup' => '<video><source src="v.webm" type="video/webm"></video>',
'#allowed_tags' => [
'video',
'source',
],
];
$output['escaped_string'] = [
'#plain_text' => '<em>This is escaped</em>',
];
JavaScript and CSS assets are specified in the render array using the #attached property (see Attaching libraries in render arrays).
Render elements
Render elements are defined by Drupal core and modules. The primary way to define a render element is to create a render element plugin. There are two types of render element plugins:
- Generic elements: Generic render element plugins implement \Drupal\Core\Render\Element\ElementInterface, are annotated with \Drupal\Core\Render\Annotation\RenderElement annotation, go in plugin namespace Element, and generally extend the \Drupal\Core\Render\Element\RenderElement base class.
- Form input elements: Render elements representing form input elements implement \Drupal\Core\Render\Element\FormElementInterface, are annotated with \Drupal\Core\Render\Annotation\FormElement annotation, go in plugin namespace Element, and generally extend the \Drupal\Core\Render\Element\FormElement base class.
See the Plugin API topic for general information on plugins. You can search for classes with the RenderElement or FormElement annotation to discover what render elements are available. API reference sites (such as https://api.drupal.org) generate lists of all existing elements from these classes. Look for the Elements link in the API Navigation block.
Modules can define render elements by defining an element plugin.
Caching
The Drupal rendering process has the ability to cache rendered output at any level in a render array hierarchy. This allows expensive calculations to be done infrequently, and speeds up page loading. See the Cache API topic for general information about the cache system.
In order to make caching possible, the following information needs to be present:
- Cache keys: Identifiers for cacheable portions of render arrays. These should be created and added for portions of a render array that involve expensive calculations in the rendering process.
- Cache contexts: Contexts that may affect rendering, such as user role and language. When no context is specified, it means that the render array does not vary by any context.
- Cache tags: Tags for data that rendering depends on, such as for individual nodes or user accounts, so that when these change the cache can be automatically invalidated. If the data consists of entities, you can use \Drupal\Core\Entity\EntityInterface::getCacheTags() to generate appropriate tags; configuration objects have a similar method.
- Cache max-age: The maximum duration for which a render array may be cached. Defaults to \Drupal\Core\Cache\Cache::PERMANENT (permanently cacheable).
Cache information is provided in the #cache property in a render array. In this property, always supply the cache contexts, tags, and max-age if a render array varies by context, depends on some modifiable data, or depends on information that's only valid for a limited time, respectively. Cache keys should only be set on the portions of a render array that should be cached. Contexts are automatically replaced with the value for the current request (e.g. the current language) and combined with the keys to form a cache ID. The cache contexts, tags, and max-age will be propagated up the render array hierarchy to determine cacheability for containing render array sections.
Here's an example of what a #cache property might contain:
'#cache' => [
'keys' => ['entity_view', 'node', $node->id()],
'contexts' => ['languages'],
'tags' => $node->getCacheTags(),
'max-age' => Cache::PERMANENT,
],
At the response level, you'll see X-Drupal-Cache-Contexts and X-Drupal-Cache-Tags headers.
See https://www.drupal.org/developing/api/8/render/arrays/cacheability for details.
Attaching libraries in render arrays
Libraries, JavaScript settings, feeds, HTML <head> tags and HTML <head> links are attached to elements using the #attached property. The #attached property is an associative array, where the keys are the attachment types and the values are the attached data.
The #attached property can also be used to specify HTTP headers and the response status code.
The #attached property allows loading of asset libraries (which may contain CSS assets, JavaScript assets, and JavaScript setting assets), JavaScript settings, feeds, HTML <head> tags and HTML <head> links. Specify an array of type => value pairs, where the type (most often 'library' — for libraries, or 'drupalSettings' — for JavaScript settings) to attach these response-level values. Example:
$build['#attached']['library'][] = 'core/jquery';
$build['#attached']['drupalSettings']['foo'] = 'bar';
$build['#attached']['feed'][] = [
$url,
$this
->t('Feed title'),
];
See \Drupal\Core\Render\AttachmentsResponseProcessorInterface for additional information.
See \Drupal\Core\Asset\LibraryDiscoveryParser::parseLibraryInfo() for more information on how to define libraries.
Placeholders in render arrays
Render arrays have a placeholder mechanism, which can be used to add data into the render array late in the rendering process. This works in a similar manner to \Drupal\Component\Render\FormattableMarkup::placeholderFormat(), with the text that ends up in the #markup property of the element at the end of the rendering process getting substitutions from placeholders that are stored in the 'placeholders' element of the #attached property.
For example, after the rest of the rendering process was done, if your render array contained:
$build['my_element'] = [
'#markup' => 'Something about @foo',
'#attached' => [
'placeholders' => [
'@foo' => ['#markup' => 'replacement'],
],
];
then #markup would end up containing 'Something about replacement'.
Note that each placeholder value *must* itself be a render array. It will be rendered, and any cache tags generated during rendering will be added to the cache tags for the markup.
The render pipeline
The term "render pipeline" refers to the process Drupal uses to take information provided by modules and render it into a response. See https://www.drupal.org/developing/api/8/render for more details on this process. For background on routing concepts, see Routing API.
There are in fact multiple render pipelines:
- Drupal always uses the Symfony render pipeline. See https://symfony.com/doc/3.4/components/http_kernel.html
- Within the Symfony render pipeline, there is a Drupal render pipeline, which handles controllers that return render arrays. (Symfony's render pipeline only knows how to deal with Response objects; this pipeline converts render arrays into Response objects.) These render arrays are considered the main content, and can be rendered into multiple formats: HTML, Ajax, dialog, and modal. Modules can add support for more formats, by implementing a main content renderer, which is a service tagged with 'render.main_content_renderer'.
- Finally, within the HTML main content renderer, there is another pipeline, to allow for rendering the page containing the main content in multiple ways: no decoration at all (just a page showing the main content) or blocks (a page with regions, with blocks positioned in regions around the main content). Modules can provide additional options, by implementing a page variant, which is a plugin annotated with \Drupal\Core\Display\Annotation\PageDisplayVariant.
Routes whose controllers return a \Symfony\Component\HttpFoundation\Response object are fully handled by the Symfony render pipeline.
Routes whose controllers return the "main content" as a render array can be requested in multiple formats (HTML, JSON, etc.) and/or in a "decorated" manner, as described above.
See also
\Symfony\Component\HttpKernel\KernelEvents::VIEW
\Drupal\Core\EventSubscriber\MainContentViewSubscriber
\Drupal\Core\Render\MainContent\MainContentRendererInterface
\Drupal\Core\Render\MainContent\HtmlRenderer
\Drupal\Core\Render\RenderEvents::SELECT_PAGE_DISPLAY_VARIANT
\Drupal\Core\Render\Plugin\DisplayVariant\SimplePageVariant
\Drupal\block\Plugin\DisplayVariant\BlockPageVariant
\Drupal\Core\Render\BareHtmlPageRenderer
File
- core/
lib/ Drupal/ Core/ Render/ theme.api.php, line 195 - Hooks and documentation related to the theme and render system.
Classes
Name | Location | Description |
---|---|---|
Element |
core/ |
Provides helper methods for Drupal render elements. |
FormElement |
core/ |
Defines a form element plugin annotation object. |
FormElement |
core/ |
Provides a base class for form element plugins. |
RenderElement |
core/ |
Defines a render element plugin annotation object. |
RenderElement |
core/ |
Provides a base class for render element plugins. |
Interfaces
Name | Location | Description |
---|---|---|
ElementInterface |
core/ |
Provides an interface for render element plugins. |
FormElementInterface |
core/ |
Provides an interface for form element plugins. |