The Drupal 8 render pipeline

Last updated on
20 March 2017

or: how Drupal renders pages

First, you need to know the general routing concepts: please read Route controllers for simple routes first.

This is explained both textually (below) and in the form of a diagram — see the files attached to this page (available in PNG, SVG and PDF — print the PDF to have a handy offline reference!).

Thumbnail of the Drupal 8 render pipeline diagram.

High level: controllers, the VIEW event and main content renderers


  1. Routes whose controllers return a Response object bypass the pipeline below. They rely directly on the Symfony render pipeline.
  2. Routes whose controllers return the "main content" as a render array automatically have the ability to be requested in multiple ways: it can be rendered in a certain format (HTML, JSON …) and/or in a certain decorated manner (e.g. with blocks around the main content).

The pipeline

This can be considered the Drupal render pipeline, but it really is just embedded within the Symfony render pipeline.

  1. After the controller returns a render array, the VIEW event will be triggered by the HttpKernel, because the controller result is not a Response, but a render array.
  2. MainContentViewSubscriber is subscribed to the VIEW event. It checks whether the controller result is a render array, and if so, it guarantees to generate a Response.
  3. Next, MainContentViewSubscriber checks whether the negotiated request format is supported:
    1. Any format for which a main content renderer service exists (an implementation of MainContentRendererInterface is supported.
    2. If the negotiated request format is not supported, a 406 JSON response is generated, which lists the supported formats in a machine-readable way (as per RFC 2616, section 10.4.7).
  4. Otherwise, when the negotiated request format is supported, the corresponding main content renderer service is initialized. A response is generated by calling MainContentRendererInterface::renderResponse() on the service. That's it!

Main content renderers

Each main content renderer service can choose how to implement its renderResponse() method. It may, of course choose to add protected helper methods to provide more structure if it's a complex main content renderer.

Drupal 8 ships with the following main content renders (and thus supports rendering any render array in one of the following formats/MIME types):

The HTML main content renderer

This can be considered the HTML main content render pipeline, but it really is just embedded within the Drupal render pipeline (see above).

The above is the high-level flow. But let's take a look at the HTML main content renderer (HtmlRenderer), because that will be used most often.

  1. HtmlRenderer::prepare() receives the main content render and ensures we have a '#type' => 'page' render array (which corresponds to <body>):
    1. if the main content render array already is '#type' => 'page', then we're done
    2. otherwise, we need to build that '#type' => 'page' render array. The RenderEvents::SELECT_PAGE_DISPLAY_VARIANT event is dispatched, to select a page display variant.
      • By default, SimplePageVariant is used, which doesn't apply any decorations.
      • But, when the Block module is enabled, BlockPageVariant is used, which allows the site builder to place blocks in any of the page regions, and hence "decorate" the main content.
      • Drupal 8 modules can subscribe to this event as well, and use a different page variant, even on a per-route basis. (Panels, Page Manager etc. can be cleanly implemented thanks to this event.)
  2. HtmlRenderer::prepare() now is guaranteed to be working on a '#type' => 'page' render array. hook_page_attachments() and hook_page_attachments_alter() are invoked. (For attaching page-level assets, that aren't bound to a specific page.)
  3. HtmlRenderer::renderResponse() uses the '#type' => 'page' render array returned by the previous step and wraps it in '#type' => 'html'. hook_page_top() and hook_page_bottom() are invoked.
  4. Renderer (formerly drupal_render()) is called on the '#type' => 'html' render array, which uses the html.html.twig template and the return value is a HTML document as a string.
  5. This HTML string is returned as the Response (specifically, a HtmlResponse object, which is a more specialized subclass of Response).

Steps 1–2 generate the <body> (page.html.twig). Steps 3–4 generate the <html> (html.html.twig). Step 5 sends a Response object containing the generated <html>.

Note: render arrays remain arrays and thus aren't rendered until step 4 above!

Rendering HTML in limited environments

For HTML pages to be rendered in limited environments, such as when you are installing or updating Drupal, or when you put it in maintenance mode, or when an error occurs, a simpler HTML page renderer is used for rendering these bare pages: BareHtmlPageRenderer.