Skip to content

The Ringroad Jinja language

A Jinja2/MiniJinja dialect extended with schema, styling, and script blocks.

Updated View as Markdown

Ringroad themes are written in a dialect of Jinja that extends the standard Jinja2 syntax with four things vanilla Jinja does not understand. The dialect compiles on MiniJinja (the Rust Jinja2 engine) only after the extension blocks have been stripped by ringroad-renderer; a vanilla Jinja processor, Python Jinja2, Tera, Liquid, etc., will crash on a Ringroad theme file.

The four extensions

# Feature Syntax Purpose
1 Role marker @editor:section / @editor:any on line 1 Tells the editor whether the node is a top-level section or a nested content atom
2 Schema block {% schema %} … {% endschema %} JSON schema: settings, accepted children, slots, presets
3 Stylesheet block {% stylesheet %} … {% endstylesheet %} CSS that ships with the node
4 JavaScript block {% javascript %} … {% endjavascript %} JS that ships with the node

A complete node file uses all four:

@editor:any
{# markup body, the node's HTML #}
<p class="rr-text">{{ node.settings.text | render_text | newlines_to_br }}</p>

{% stylesheet %}
.rr-text { color: var(--color-base-content); }
{% endstylesheet %}

{% schema %}
{
  "name": "Text",
  "role": "simple",
  "settings": [ … ]
}
{% endschema %}

Why vanilla processors crash

MiniJinja rejects {% schema %}, {% stylesheet %}, and {% javascript %} as unknown statements. The engine’s theme loader strips those three block types from the source (string search for {% tag %}{% endtag %}

  • see strip_metadata_blocks in src/theme/loader.rs) before registering the template, and extracts the @editor: role marker separately. What MiniJinja actually compiles is the markup body only.

The stripped content isn’t lost, it becomes the editor schema and the asset pipeline:

flowchart LR
  F[hero.jinja] --> X{loader pass}
  X --> M[strip schema/stylesheet/javascript blocks]
  X --> S["{% schema %} → NodeSchema in manifest"]
  X --> C["{% stylesheet %} → asset pipeline CSS"]
  X --> J["{% javascript %} → asset pipeline JS"]
  M --> T[MiniJinja: default/nodes/hero.jinja]

What’s still vanilla Jinja

Everything else is ordinary Jinja2/MiniJinja, including the node body’s template logic:

  • Expressions and filters: {{ node.settings.heading }}, {{ item.price | money }}, {{ count | pluralize }}
  • Tags: {% if %}, {% for %}, {% set %}, {% block %}, {% extends %}, {% include %}, {% from %} … {% import %}, {% macro %}
  • Whitespace control and comment syntax
  • Built-in filters: safe, default, length, urlencode, lower, upper, replace, trim, math operators, etc.

Two runtime behaviors differ from Python Jinja2 and matter for theme authors:

  1. Lenient undefined, the engine sets UndefinedBehavior::Lenient, so referencing a missing variable renders as an empty string instead of raising. {{ product.name }} on a page where product is absent renders ``, no crash, no conditional wrapper needed. Optional objects are additionally backed by StubObject wrappers that return empty/false for any property.
  2. Never-crash rendering, render_node and render_text swallow their own errors (empty string / original string returned), so a broken child or a malformed merchant-entered string degrades gracefully.

The runtime surface

Templates get a fixed set of globals, one per-node node object, and a handful of custom filters/functions:

Kind Names
Globals shop, settings, product, collection, collections, cart, page, search, blog, article, linklists, request, locale, extra, store
Node-local node (settings, children, slots, attributes)
Filters money, pluralize, newlines_to_br, render_text
Function render_node(child)

All of these are documented in Templates and Globals reference.

Styling & script caveat

CSS and JS in node blocks are emitted once per rendered type on a page, not once per node instance, so if a page renders the same node type twice, its stylesheet/script still appears once. However, if a node includes its own body twice (for example a navbar rendered in both desktop and mobile markup), the CSS/JS in the block can end up duplicated in the output. The default theme lives with this today; see Assets blocks for the details.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close