render_block and Def Scope

Blocks do not inherit defs from the same template

2 min read 330 words

When you calltemplate.render_block("block_name", context), the block is rendered in isolation. It does not have access to {% def %}macros defined in the same template.

Inherited blocks:render_block() resolves blocks through the inheritance chain. You can render parent-only blocks (e.g. sidebarfrom a base template) on a descendant template. Child overrides still win. See Inheritance — render_block.

The Problem

{# page.html — def and block in same file #}
{% def helper() %}shared logic{% end %}

{% block content %}
  {{ helper() }}  {# NameError or UndefinedError when using render_block #}
{% end %}

If a framework (e.g. Chirp) calls template.render_block("content", ...), the block cannot see helperbecause blocks are compiled with their own scope.

Fix

Split defs into a separate file and import them:

{# _helpers.html #}
{% def helper() %}shared logic{% end %}
{# page.html #}
{% from "_helpers.html" import helper %}

{% block content %}
  {{ helper() }}
{% end %}

Now both full-page render() and render_block("content", ...) work, because the block imports helperfrom another template.

Regions: An Alternative

{% region %}blocks are compiled as part of the template and have access to defs and imports from the same file. If you need a parameterized block that uses defs, consider a region instead of splitting into a separate template:

{% from "_helpers.html" import helper %}

{% region sidebar(current_path="/") %}
  {{ helper(current_path) }}
{% end %}

Both render_block("sidebar", ...) and {{ sidebar(...) }} work, and the region body can call helper(). See Functions — Regions.

When This Matters

  • Fragment rendering — Chirp and similar frameworks userender_block()to return HTML fragments for htmx, SSE, etc.
  • Block caching — Site-scoped block caching renders blocks individually viarender_block().

See Also