Among the many components that power the Drupal 7 CMS platform, one of the most difficult to master is render arrays.  These are PHP keyed arrays whose keys have special meanings, and which are “magically” converted into HTML markup.  In theory, this makes for an elegant, extensible format, where any number of manipulations can occur before the HTML is actually generated.  However, in practice, so many things can affect the final output of a render array that it can be difficult to make it do what you want.  If you look at a render array in code, it is difficult to guess how exactly the corresponding markup will look. Correspondingly, if it doesn’t look how you expect, it can be difficult to determine how to fix it.

The purpose of this guide is to help you understand how render arrays work and which options are available. You should then be able to look through Drupal’s built-in render types, defined in [cci_php]system_element_info()[/cci_php] and leverage them more effectively in your own applications.

Types of keys

The first thing to make sure you understand is the difference between value keys and content keys.  Value keys always begin with a hash (#) and contain some value that is usually passed directly to a theme function or to Drupal’s rendering engine.  These always have special names with specific meanings.  On the other hand, child keys – those that do not begin with a hash – contain nested elements and can have any name. The rendering engine sorts any child elements using the [cci_php]’#weight'[/cci_php] key, then recurses into them renders them first before rendering the outer elements. See [cci_php]element_children()[/cci_php] for more on how the child elements are identified and sorted.

For example, consider the following:
[cce_php]
$render = array(
‘#type’ => ‘container’,
‘#attributes’ => array(‘class’ => array(‘test’)),
‘content’ => array(
‘#type’ => ‘markup’,
‘#markup’ => t(‘This is my test markup’),
‘#weight’ => 0,
),
‘link’ => array(
‘#type’ => ‘link’,
‘#title’ => t(‘My link’),
‘#href’ => ‘node/1’,
‘#weight’ => 1,
),
);
[/cce_php]
In this example, [cci_php]’#type'[/cci_php], [cci_php]’#attributes'[/cci_php], [cci_php]’#markup'[/cci_php] , [cci_php]’#title'[/cci_php], [cci_php]’#href'[/cci_php] and [cci_php]’#weight'[/cci_php] are value keys which have specific meanings, while [cci_php]’content'[/cci_php] and [cci_php]’link'[/cci_php] are nested elements containing some text and a link to render. Each of these nested elements will be processed in turn.

Special Value Keys

While some value keys are standard for all render arrays or forms, most are actually dependent on the theme that ultimately renders each element. These keys can be found in [cci_php]drupal_render()[/cci_php], [cci_php]form_builder()[/cci_php], and [cci_php]element_children()[/cci_php].

Any key that is not listed in one of these functions is instead used by the theme functions. The theme functions that make up forms try to do a good job of standardizing keys and reusing them across themes; these are documented here. However, this reference does not include non-form render array types, which may or may not use these same keys. For instance, in the example above, the [cci_php]’container'[/cci_php] type expects the [cci_php]’#attributes'[/cci_php] key, but the [cci_php]’markup'[/cci_php] type does not, so passing an [cci_php]’#attributes'[/cci_php] key to that element won’t do anything. Similarly, the [cci_php]’#title'[/cci_php] and [cci_php]’#href'[/cci_php] keys would be meaningless in the [cci_php]’container'[/cci_php] element.

Theme functions

Although all previous versions of Drupal use theme functions, Drupal 7 is the first to extend them to render arrays, using the special [cci_php]’#theme'[/cci_php] key.  But, like many things in Drupal, there are multiple ways to implement them.

Variables

One way to define a theme is to list each of the variables that will be passed in.  Any value keys are stripped of their hash and are converted into variables within the theme function.  However, this sort of theme function most likely can’t handle nested elements. This means that any nested items need to be rendered completely before they are added to the render array.

For example:
[cce_php]
/**
* Implements hook_theme()
*/
function system_theme() {
$themes = array(
// …
// Lists of items
‘item_list’ => array(
‘variables’ => array(‘items’ => array(), ‘title’ => NULL, ‘type’ => ‘ul’, ‘attributes’ => array()),
),
// ..
);
return $themes;
}

function theme_item_list($variables) {
// Retrieve the passed-in variables. The hash has been stripped out.
$items = $variables[‘items’];
$title = $variables[‘title’];
$type = $variables[‘type’];
$attributes = $variables[‘attributes’];

$output = ‘

‘;
if (isset($title)) {
$output .= ‘

‘ . $title . ‘

‘;
}
// …
}

function mymodule_page() {
$items = array(
l(t(‘First link’), ‘node/1’),
array(
‘data’ => l(t(‘Second link’), ‘node/2’),
‘class’ => array(‘item’, ‘item-2’),
),
);
// Add a hash before each passed-in variable
$out = array(
‘#theme’ => ‘item_list’,
‘#items’ => $items,
‘#title’ => t(‘My Links’),
‘#attributes’ => array(‘class’ => array(‘my-links’)),
);
return $out;
}
[/cce_php]
See the complete implementation of [cci_php]theme_item_list()[/cci_php].

When this page is rendered as HTML, it looks something like this:
[cc lang=”html”]

[/cc]

Render Elements

Render element themes allow you to pass an entire render array into a theme function. These types of theme functions tend to work better with nested elements, or as theme wrappers (see below).

For example:
[cce_php]
/**
* Implements hook_theme()
*/
function system_theme() {
$themes = array(
// …
// Container for nested elements
‘container’ => array(
‘render element’ => ‘element’,
),
// ..
);
return $themes;
}

function theme_container($variables) {
// Get the passed in element, whose keys are intact.
$element = $variables[‘element’];
// …
return ‘

‘ . $element[‘#children’] . ‘

‘;
}

function mymodule_page2() {
$render = array(
‘#theme_wrappers’ => array(‘container’),
‘#attributes’ => array(‘class’ => array(‘test’)),
‘content’ => array(
‘#markup’ => t(‘This is my test markup’),
),
);
return $render;
}
[/cce_php]
See the complete implementation of [cci_php]theme_container()[/cci_php].

When this page is rendered as HTML, it looks something like this:
[cc lang=”html”]

This is my test markup

[/cc]

Theme wrappers

In addition to the [cci_php]’#theme'[/cci_php] key, render arrays can also have [cci_php]’#theme_wrappers'[/cci_php]. These take the output of the inner theme function and wrap additional markup around it. An element can have many theme wrappers; each will wrap the markup that was generated by the previous theme functions, which can be found in the [cci_php]’#children'[/cci_php] key of the element.

Theme wrapper functions have access to the same keys in the same element as any theme functions used before on the same element, so it’s possible for a value key, such as [cci_php]’#title'[/cci_php], to be used more than once in different theme functions. In Drupal’s built-in types, however, each theme function operates on a different set of keys.

Form Preprocessors

In Drupal 7, forms are just a special case of render arrays, but a lot of additional processing is applied to them. In addition to the main theme functions, a set of processor functions can be called first. This allows all sorts of customizations and mischief to occur.

Element Types

Since this is all very complicated and hard to remember, Drupal provides a mechanism for automating much it: the [cci_php]’#type'[/cci_php] key. Each type is defined by [cci_php]hook_element_info()[/cci_php], which really just defines a set of default values for the render element.

For example:
[cce_php]
/**
* Implements hook_element_info().
*/
function system_element_info() {
// …
$types[‘container’] = array(
‘#theme_wrappers’ => array(‘container’),
‘#process’ => array(‘form_process_container’),
);
// …
return $types;
}

function mymodule_page3() {
$render1 = array(
‘#type’ => ‘container’,
‘content’ => ‘…’,
);
$render2 = array(
‘#theme_wrappers’ => array(‘container’),
‘#process’ => array(‘form_process_container’), // Ignored unless this is in a form
‘content’ => ‘…’,
);
// $render1 and $render2 will produce exactly the same markup.
}
[/cce_php]
Special case: if a render element contains a [cci_php]’#markup'[/cci_php] key and no [cci_php]’#type'[/cci_php] key, the type is assumed to be [cci_php]’markup'[/cci_php].

Prefixes and suffixes

Another common set of value keys supported by all render elements is [cci_php]’#prefix'[/cci_php] and [cci_php]’#suffix'[/cci_php], which insert arbitrary markup around the rendered markup for the render array. These can be used for markup that is otherwise hard to produce using the built-in types, and without the need for a special theme or theme override. However, try to use render arrays to output nested markup where possible, since they are easier to edit, add classes to, etc.

For example, both of these produce the same result, but the first one is easier to override and add classes to:
[cce_php]
function mymodule_page4() {
$render = array(
‘container1’ => array(
‘#type’ => ‘container’,
‘#attributes’ => array(‘class’ => array(‘container1’)),
‘content’ => array(
‘#markup’ => t(‘Container 1’),
),
),
‘container2’ => array(
‘#prefix’ => ‘

‘,
‘#markup’ => t(‘Container 2’),
‘#suffix’ => ‘

‘,
),
);
}
[/cce_php]
When rendered as HTML, this produces:
[cc_html]

Container 1
Container 2

[/cc_html]

Form fields use a theme wrapper function, [cci_php]theme_form_element()[/cci_php], that also accepts [cci_php]’#field_prefix'[/cci_php] and [cci_php]’#field_suffix'[/cci_php] as valid keys. This markup appears within spans before and after the form field. However, these are not standard for other types of elements.

Putting it all together

Render arrays are built and converted to markup in the following order:

  • [cci_php]’#type'[/cci_php] (string): Applies all default values defined in a hook_element_info() implementation. This can add any of the other processor types.
  • [cci_php]’#process'[/cci_php] (forms only) (array): Applies processing to form elements just after user input has been processed. These are typically used to expand an element into multiple elements.
  • [cci_php]’#after_build'[/cci_php] (forms only) (array): Applies processing to form elements after child elements have been built and processed.
  • [cci_php]’#pre_render'[/cci_php] (array): Applies processing to all render elements (inside or outside of forms) before the theme function is called.
  • [cci_php]’#theme'[/cci_php] (string): Applies a theme function to the element. Each theme function might have its own set of overrides, theme hook suggestions and other modifications.
  • [cci_php]’#theme_wrappers'[/cci_php] (array): Applies one or more theme functions to the results of the inner theme function. These manipulate the markup in the [cci_php]’#children'[/cci_php] key.
  • [cci_php]’#post_render'[/cci_php] (array): Applies processing to the rendered markup emitted by the theme functions, in the [cci_php]’#children'[/cci_php] key.

With the exception of [cci_php]’#type'[/cci_php] and [cci_php]’#theme'[/cci_php], which take a string, and [cci_php]’#theme_wrappers'[/cci_php], which takes an array of strings, all the rest of these keys expect an array of function names. If the function exists, it is called and passed the render element as input.

Here, for example, is a type of element that contains many of these:
[cce_php]
/**
* Implements hook_element_info().
*/
function system_element_info() {
// …
$types[‘textfield’] = array(
‘#input’ => TRUE, // Tells the forms engine that this element accepts user input
‘#size’ => 60, // A default value
‘#maxlength’ => 128, // A default value
‘#autocomplete_path’ => FALSE, // Allows autocomplete functionality to be attached
‘#process’ => array(‘ajax_process_form’), // A process function (handles any ‘#ajax’ elements)
‘#theme’ => ‘textfield’, // The theme function
‘#theme_wrappers’ => array(‘form_element’), // A theme function used to wrap all form elements
);
// …
return $types;
}
[/cce_php]

Everything must attach to a theme!

In order to show up on a page, every element and nested element of a render array must ultimately be attached to a theme function.

This could be done explictly by setting [cci_php]’#theme'[/cci_php] and/or [cci_php]’#theme_wrappers'[/cci_php], or implicitly by setting the [cci_php]’#type'[/cci_php] or providing [cci_php]’#markup'[/cci_php]. Or the element could be used within a theme attached to a parent element. Or the parent element could be used to group its child elements while not rendering any markup itself. But without at least one of these things, a portion of a render array will never actually be rendered. For example:

[cce_php]
function mymodule_page5() {
$render = array(
‘container1’ => array(
‘#type’ => ‘container’,
‘#attributes’ => array(‘class’ => array(‘container-1 visible’)),
‘element1’ => array(
‘#markup’ => t(‘This is element 1’),
),
‘element2’ => t(‘This is not an element and will never be rendered’),
),
‘container2’ => array(
‘#attributes’ => array(‘class’ => array(‘container-2 never-rendered’)),
‘#description’ => t(‘This container has no theme, so its children will still be rendered but it will not add anything’),
‘element3’ => array(
‘#type’ => ‘link’,
‘#href’ => ‘node/3’,
‘#title’ => t(‘This is a link, which will be be rendered outside of the container’),
)
‘element4’ => array(
‘#theme’ => ‘item_list’,
‘#items’ => array(t(‘Item 1’)),
),
‘element5’ => array(
‘#title’ => t(‘This element has no theme or markup, so it will not be rendered’),
),
)
);
return $render;
}
[/cce_php]

When rendered, this produces:
[cc_html]

This is element 1

This is a link, which will be rendered outside of the container

  • Item 1

[/cc_html]

Further resources

Each of the features described above performs some special functionality that can appear to be “magic.” In fact, everything behaves in a rational manner; it just might require threading your way through multiple layers of code to understand it.

If you’d like to dig into the functionality, here are the Drupal functions that actually perform the magic:

In addition, here is a reference to the available keys for all form element types. Unfortunately it does not cover other render types, nor show how these keys are actually used.

However, with this guide, the documentation for [cci_php]system_element_info()[/cci_php], and a lot of patience, you can find out how each of the render types is defined, what they do, and how to use them effectively.