Last updated on
9 January 2017

Drupal 8's Render API automatically creates placeholders of highly dynamic parts of a page, to have the best possible cacheability (i.e. the fewest possible contexts to vary by) for those pages.


  • Some cache contexts have a high cardinality and are thus very costly because they cause many, many variations. A good example is the 'user' cache context.
  • Some cache tags have a high invalidation rate: they're known to be invalidated very frequently, which makes caching anything that has this cache tag (i.e. depends on the data this cache tag describes) not very worthwhile.
  • Caching something with max-age zero is completely useless because it can not be cached at all. But, for some sites, some content is/needs to be updated with a very high frequency, say every second (max-age=1) or every few seconds (max-age=5). Depending on your site's server infrastructure and needs, it may also not be worth caching things with such a low max-age.

In other words, for all three cacheability metadata properties, some values cause poor cacheability and due to bubbling, they will infect the rest of the page too.

(All ancestors inherit the cacheability metadata so all ancestors will be poorly cacheable, will therefore not be cached, including the containing page.)


The process of detecting poorly cacheable (highly dynamic) parts of a page and rendering them later is called auto-placeholdering.

If a certain render array is built lazily (using a #lazy_builder callback) and has the 'user' cache context, Drupal is able to postpone the rendering until the very last moment. The place in the page where that very dynamic content would appear is first assigned a placeholder, and only at the very last moment, it is replaced with the actual content.

This allows Drupal to still:

  • render cache fragments (blocks, entities …) despite parts of them being too dynamic to be worth caching.
  • cache the overall page in the Dynamic Page Cache.

Auto-placeholdering is performed according to the auto-placeholder conditions specified in the renderer.config container parameter:

      max-age: 0
      contexts: ['session', 'user']
      tags: []

This can be customized to match your site's needs, but as you can tell, by default Drupal will automatically placeholder fragments of the page that have a max-age of zero, or vary by session or by the current user. No high-invalidation rate cache tags are specified — because that requires site-specific knowledge.


An HTML page served by Drupal can be considered one big render tree, with the root of the tree being the entire page, the first level being the regions, the second level the blocks, the third level being the block contents, and so on.

Therefore, any subtree will be auto-placeholdered, if:

  1. it is defined by a #lazy_builder (and not by a render array)
  2. it meets the auto-placeholder conditions, which can happen in one of two ways:
    1. it has a #cache value whose properties meet at least one of the auto-placeholder conditions
    2. its #lazy_builder callback, when executed and rendered, results in the bubbling of cacheability metadata that meet at least one of the auto-placeholder conditions. The first time this happens, the bubbled cacheability metadata will be cached (see PlaceholderingRenderCache) so that future hits won't have to first execute and render the #lazy_builder to know it should be auto-placeholdered
      Note: auto-placeholdering based on a bubbled max-age does not yet work in 8.0.x, that's postponed to 8.1.0: Auto-placeholdering based on a bubbled cache context or cache tag does work in 8.0.x.


See CommentDefaultFormatter (comment form), BlockViewBuilder (blocks), NodeViewBuilder (node links), StatusMessages (status messages).

See also