Skip to main content

Render API overview and Render Element

Render API overview

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:

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:

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.

 

RenderElement

Render elements are referenced in render arrays;
see the Render API topic for an overview of render arrays and render elements.

The elements of render arrays are divided up into properties (whose keys start with #) and children (whose keys do not start with #). The properties provide data or settings that are used in rendering. Some properties are specific to a particular type of render element, some are available for any render element, and some are available for any form input element. A list of the properties that are available for all render elements follow; the properties that are for all form elements are documented on \Drupal\Core\Render\Element\FormElement, and properties specific to a particular element are documented on that element's class. See the Render API topic for a list of the most commonly-used properties.

Many of the properties are strings that are displayed to users. These strings, if they are literals provided by your module, should be internationalized and translated; see the Internationalization topic for more information. Note that although in the properties list that follows, they are designated to be of type string, they would generally end up being \Drupal\Core\StringTranslation\TranslatableMarkup objects instead.

Here is the list of the properties used during the rendering of all render elements:

  • #access: (bool) Whether the element is accessible or not. When FALSE, the element is not rendered and user-submitted values are not taken into consideration.
  • #access_callback: A callable or function name to call to check access. Argument: element.
  • #allowed_tags: (array) Array of allowed HTML tags for XSS filtering of #markup, #prefix, #suffix, etc.
  • #attached: (array) Array of attachments associated with the element. See the "Attaching libraries in render arrays" section of the Render API topic for an overview, and \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments for a list of what this can contain. Besides this list, it may also contain a 'placeholders' element; see the Placeholders section of the Render API topic for an overview.
  • #attributes: (array) HTML attributes for the element. The first-level keys are the attribute names, such as 'class', and the attributes are usually given as an array of string values to apply to that attribute (the rendering system will concatenate them together into a string in the HTML output).
  • #cache: (array) Cache information. See the Caching section of the Render API topic for more information.
  • #children: (array, internal) Array of child elements of this element. Set and used during the rendering process.
  • #create_placeholder: (bool) TRUE if the element has placeholders that are generated by #lazy_builder callbacks. Set internally during rendering in some cases. See also #attached.
  • #defaults_loaded: (bool) Set to TRUE during rendering when the defaults for the element #type have been added to the element.
  • #value: (mixed) A value that cannot be edited by the user.
  • #has_garbage_value: (bool) Internal only. Set to TRUE to indicate that the #value property of an element should not be used or processed.
  • #id: (string) The HTML ID on the element. This is automatically set for form elements, but not for all render elements; you can override the default value or add an ID by setting this property.
  • #lazy_builder: (array) Array whose first element is a lazy building callback (callable), and whose second is an array of scalar arguments to the callback. To use lazy building, the element array must be very simple: no properties except #lazy_builder, #cache, #weight, and #create_placeholder, and no children. A lazy builder callback typically generates #markup and/or placeholders; see the Placeholders section of the Render API topic for information about placeholders.
  • #markup: (string) During rendering, this will be set to the HTML markup output. It can also be set on input, as a fallback if there is no theming for the element. This will be filtered for XSS problems during rendering; see also #plain_text and #allowed_tags.
  • #plain_text: (string) Elements can set this instead of #markup. All HTML tags will be escaped in this text, and if both #plain_text and #markup are provided, #plain_text is used.
  • #post_render: (array) Array of callables or function names, which are called after the element is rendered. Arguments: rendered element string, children.
  • #pre_render: (array) Array of callables or function names, which are called just before the element is rendered. Argument: $element. Return value: an altered $element.
  • #prefix: (string) Text to render before the entire element output. See also #suffix. If it is not already wrapped in a safe markup object, will be filtered for XSS safety.
  • #printed: (bool, internal) Set to TRUE when an element and its children have been rendered.
  • #render_children: (bool, internal) Set to FALSE by the rendering process if the #theme call should be bypassed (normally, the theme is used to render the children). Set to TRUE by the rendering process if the children should be rendered by rendering each one separately and concatenating.
  • #suffix: (string) Text to render after the entire element output. See also #prefix. If it is not already wrapped in a safe markup object, will be filtered for XSS safety.
  • #theme: (string) Name of the theme hook to use to render the element. A default is generally set for elements; users of the element can override this (typically by adding __suggestion suffixes).
  • #theme_wrappers: (array) Array of theme hooks, which are invoked after the element and children are rendered, and before #post_render functions.
  • #type: (string) The machine name of the type of render/form element.
  • #weight: (float) The sort order for rendering, with lower numbers coming before higher numbers. Default if not provided is zero; elements with the same weight are rendered in the order they appear in the render array.

 

Tested Example

        $form['pagenotfound'] = [            
            '#type' => 'table',
            '#caption' => $this->t('page-not-found table'),
            '#header' => [
                'idx' => $this->t('index'),
                'ip' => $this->t('ip'),
                'visits' => $this->t('visits'),
                'operate' => $this->t('operate'),
            ],
            '#empty' => $this->t('no page-not-found statistics '),
            '#tableselect' => TRUE,
            '#sticky' => TRUE,
            '#attributes' => [
                'class' => ['notfound-table sentry-table'],
            ],
            '#prefix' => '<div class="notfound-table-box table-box">',
            '#suffix' => '</div>',            
        ];