In a Django project, what changes when you configure the Jinja2 template backend alongside or instead of DjangoTemplates?
answer
- a different BACKEND path
- a different app subdirectory
- an environment callable
- only three globals come free
- the admin still needs the DTL
basics
~20 sThe Jinja2 backend loads templates from each app's jinja2/ folder, knows none of Django's tags or filters, adds only request, csrf_input and csrf_token globals, and is customised through an environment callable; the admin still requires a DjangoTemplates engine.
solid answer
~40 sSetting `BACKEND` to `django.template.backends.jinja2.Jinja2` (with the `jinja2` package installed) swaps the language, not just the syntax. With `APP_DIRS: True` it looks in each app's **`jinja2/`** subdirectory, not `templates/`. Django's tags and filters such as `{% url %}` and `{% static %}` do not exist there; you expose `reverse`, `static` and helpers as globals from a callable named in `OPTIONS["environment"]`. When rendered with a request, the backend adds only `request`, `csrf_input` and `csrf_token`; `context_processors` is accepted but Django's docs discourage it in favour of globals. Django sets `autoescape=True`, `auto_reload` to `DEBUG` and a debug-friendly `undefined` by default. Most projects configure **both** engines, because the admin requires a `DjangoTemplates` engine (system check `admin.E403`), and `get_template()` tries engines in order.
code
html · 4 lines{# storefront/jinja2/storefront/home.html, rendered by the Jinja2 backend #}
<link rel="stylesheet" href="{{ static('css/site.css') }}">
<a href="{{ url('stores:cart') }}">Cart</a>
<form method="post">{{ csrf_input }}<button>Subscribe</button></form>go deeper
Know that Django supports two template engines, the Django template language and Jinja2, chosen by BACKEND in TEMPLATES.
Explain the practical differences: the jinja2 app folder, no Django tags, the environment callable, and the request, csrf_input and csrf_token globals.
Run both engines safely, keep the admin on the DTL, and move per-page helpers into environment globals instead of context processors.
Weigh a second template language against its costs: duplicated helpers, third-party apps that ship DTL templates, and team familiarity.
## Two built-in backends `TEMPLATES[...]["BACKEND"]` chooses the template engine. Django ships two: - `django.template.backends.django.DjangoTemplates` — the **Django template language (DTL)**; - `django.template.backends.jinja2.Jinja2` — an adapter around the separately installed **Jinja2** library. Both plug into the same loading API (`get_template()`, `render_to_string()`, `render()`), so views do not change. What changes is where templates live, what the templates can call and what arrives in the context. ## What changes, side by side | Aspect | DjangoTemplates | Jinja2 backend | |---|---|---| | App folder searched with `APP_DIRS` | `<app>/templates/` | `<app>/jinja2/` | | Django tags and filters (`url`, `static`, `csrf_token`, `date`) | built in | not available | | Calling functions with arguments in templates | not supported; use tags | supported | | Customisation | `OPTIONS` such as `libraries`, `builtins`, `loaders` | `OPTIONS["environment"]`: dotted path to a callable returning a `jinja2.Environment` | | Context with a request | context processors through `RequestContext` | `request`, `csrf_input`, `csrf_token`, plus optional `context_processors` | | Autoescape default | on | on (Django sets `autoescape=True`) | | Reload on edit | cached loader, reset by the dev autoreloader | `auto_reload` defaults to `settings.DEBUG` | | Undefined variables | render as the empty string by default | `DebugUndefined` when `DEBUG`, else `Undefined` | ## Wiring Django helpers into Jinja2 Because Jinja2 does not know Django's tags, Django's documentation shows an environment callable that adds the functions templates need: ```python # storefront/jinja2.py from django.templatetags.static import static from django.urls import reverse from jinja2 import Environment def environment(**options): env = Environment(**options) env.globals.update({"static": static, "url": reverse}) return env ``` ```python TEMPLATES = [ { "BACKEND": "django.template.backends.jinja2.Jinja2", "DIRS": [BASE_DIR / "jinja2"], "APP_DIRS": True, "OPTIONS": {"environment": "storefront.jinja2.environment"}, }, { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, "OPTIONS": {"context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ]}, }, ] ``` Templates then write `{{ url('stores:home') }}` and `{{ static('css/site.css') }}`, and a form includes `{{ csrf_input }}`. ## Context processors under Jinja2 The `context_processors` option exists, but Django's docs **discourage** it: Jinja2 templates can call functions, so a helper placed in `env.globals` and called as `{{ store_branding(request) }}` does the same job without running on every render. The docs keep processors for the narrow case of an expensive, request-dependent value needed several times in every template. ## Running both engines 1. **The admin requires the DTL.** Its system check `admin.E403` fails unless a `DjangoTemplates` engine is configured. 2. **Engines are tried in order.** `get_template("x.html")` asks each engine in turn; the separate `jinja2/` folders keep names from colliding. 3. **Choose explicitly when needed** with `using=`, whose value is the engine's `NAME` (by default the next-to-last part of the backend path: `jinja2` or `django`). 4. **Third-party apps** ship DTL templates, so they keep working through the DTL engine. ## When teams choose Jinja2 - Template-heavy pages where calling functions with arguments simplifies logic. - Teams already fluent in Jinja2 syntax. The costs are two template languages in one project, reimplemented helpers for everything Django provides as tags, and reusable apps that only ship DTL templates.
- Why does manage.py check report admin.E403 after switching the only engine to Jinja2?The admin's templates are written in the Django template language, and its checks require at least one `DjangoTemplates` engine in `TEMPLATES`. Keep a DTL engine alongside Jinja2; the admin and other third-party apps keep using it.
- How do you render a specific template with the Jinja2 engine when both engines could find the name?Pass the engine alias: `render_to_string("storefront/home.html", ctx, request=request, using="jinja2")`. The alias is the engine's `NAME`, which defaults to `jinja2` for the Jinja2 backend.
saying these in an interview costs you the question
- Django's {% url %} and {% static %} tags work unchanged in Jinja2 templates.
- With APP_DIRS the Jinja2 backend reads each app's templates/ folder.
- The Jinja2 backend runs the same context processors as DjangoTemplates by default.
- Switching every engine to Jinja2 is fine for the Django admin.
- The Jinja2 backend disables autoescaping to match Jinja2's own default.