Template resolution
We've been calling fluid.render("index.html") and extending
fluid_base.html without ever explaining where those names actually point. This
chapter pulls back the curtain — which matters, because the name you pass decides
which file wins.
Rendering, the short version
fluid.render(template, **ctx) does two things: it runs every registered context
processor (that's where the surface variables from the previous chapter come from) and then
renders the template through the async Jinja environment. The template argument
is a name, and that name is looked up against a stack of loaders.
Its sibling fluid.render_string(source, **ctx) does the same for a template you
hold as a string instead of a file — same context processors, same environment, no
lookup. Reach for it when the template comes from your database or from a user, and for
nothing else: a string has no name, so it can neither be cached nor extended.
render() no longer takes an is_string flag. If you carry one over
from an alpha it is not an error — it quietly becomes an ordinary template variable
while your source string is looked up as a file name, and you get a
TemplateNotFound naming your entire template. Use render_string.
Three sources, in order
When your app starts, the framework assembles the Jinja loader from three layers and searches them in this exact order:
-
Your app — everything inside
fluid/templates, searched first for a plain name. - Additives — each enabled Additive contributes its templates under its own id namespace, so they never shadow your unprefixed names (more on that in the Additives chapters).
-
The framework — the bundled templates like
fluid_base.htmland the error pages.
Because your app sits before the framework, an unprefixed name resolves to your file first and falls through to the framework only if you don't have one. So the names map to disk like this:
render("index.html") -> fluid/templates/index.html (your app)
render("docs/intro.html") -> fluid/templates/docs/intro.html (your app)
render("fluid_base.html") -> bundled with the framework
render("errors/404.html") -> bundled with the framework
The path you pass is always relative to fluid/templates. Nest as deeply as you like: the documentation site you are reading right now renders pages with names such as docs/1.0.0b2/get-started.html, which is just a file at that path inside its fluid/templates.
Overriding framework templates
That ordering has a useful consequence: since your app is searched first, you can replace
any framework template by simply creating a file with the same name. Want your own 404 page
or your own base layout? Drop it into fluid/templates and it takes over —
no configuration needed:
{% extends "fluid_base.html" %}
{% block content %}
<h1>{{ _('Lost at sea') }}</h1>
{% endblock %}
Explicit namespaces
Sometimes you want to be unambiguous about which layer you mean. Each source also answers to a prefix, so you can skip the fall-through entirely:
-
app/…— force your app's templates, e.g.app/index.html. -
fluid/…— force the framework's templates, e.g.fluid/fluid_base.html. Handy when you have overridden a name locally but still want to extend the original. -
<additive_id>/…— an Additive's templates always live under its id.
This is why Additives never collide with your app: their files are only ever reachable
through their own id namespace. An Additive's own render() adds that prefix for
you automatically, so inside an Additive you keep writing plain names.
What the framework gives you
For reference, these are the templates you can always extend or override:
fluid_base.html— the default page layout.base_email.html— a starting point for HTML mails.errors/<status>.html— the default error pages for the common HTTP statuses (400, 401, 403, 404, 405, 429, 500, 502, 503), rendered automatically by the processing layer. In debug mode a detailederrors/debug/500.htmltakes over for unhandled exceptions.
Those pages are only rendered for a client that asks for HTML. Everything else gets JSON, and outside debug mode that JSON says nothing but the status text — the exception's message, its type and its traceback are debug-only, so a production 500 cannot hand an api client the sql fragment or connection string the exception happened to carry.
When the stack is sealed
The loader stack is assembled once, at startup, and then frozen. Additives add theirs while
they are being enabled; if you ever need to contribute one yourself, that is what
fluid.add_template_loader(loader) is for, and it has to happen before the server
comes up.
Freezing buys you the second half: outside debug mode Jinja stops checking whether a template
changed on disk, because nothing can. In debug mode it does check, so your edits show up on
reload — which is also why a page renders noticeably faster in production than it does
while you are writing it.
Keep this resolution order in mind when you name your pages. As long as your page lives under fluid/templates and doesn't shadow a framework name you rely on, you are free to structure the directory however suits your project.
Continue reading
From here you can continue straight with the Additives introduction.