When is it good to use simple_tag in Django templates
In Django, there are usually two ways to pass data to a template.
- Inject via context in the view: explicitly provide the data needed to render the page.
- Inject globally via a context processor: automatically add data that is common to all templates.
There is a third option: simple_tag (custom template tag).
When used well, it keeps the view thin and the template clean. When misused, it can obscure where data originates, making maintenance harder.

This article does not aim to provide a definitive answer; instead, it shares the criteria I use in practice to decide when to employ a simple_tag.
Defining simple_tag in one sentence
A function that takes existing template values and returns a lightly processed result.
- It can be called on demand, like pulling data.
- It extracts formatting, composition, or conversion logic out of the template.
The boundary with traditional approaches
View injection: “core data for this page”
If the data is essential for rendering the page, I always let the view handle it.
- Central lists or detail data that define the page layout.
- Results of branching logic based on conditions.
- Model data fetched via ORM queries.
These are part of the request‑handling flow, so moving them to a template tag can make tracking difficult.
Context processor: “global data present in every template”
Examples:
request- Logged‑in user information
- Site‑wide settings, menus, environment values
- Global UI data such as badge or notification counts
But context processors have drawbacks.
- They appear automatically everywhere, making usage hard to trace.
- Heavy logic can increase the cost of every page.
Therefore, I keep global data to a minimum and delegate global data processing or composition to simple_tag.
Criteria for using simple_tag
My rule set is simple:
- Is this data core to rendering the page? → view
- Does the ORM touch the data even 1%? → view
- Can it be built by combining or transforming request + context processor values? → simple_tag
- Is it a helper logic separate from the view’s main flow? → simple_tag
The key is:
- Fetching data (especially from the DB) belongs to the view.
- Formatting data for presentation belongs to
simple_tag.
Why “ORM always goes to the view”
Starting ORM queries inside a template tag often leads to problems.
- N+1 queries when looping and calling a tag.
- Difficulty pinpointing where the query originates.
- Ambiguity about where to apply caching or prefetching.
So I take a conservative stance:
- All DB‑touching logic lives in the view (or service layer).
simple_tagonly processes data that already exists.
Most common pattern: combining request + global context for output
I make the request object and custom context processor values available to every template, and use simple_tag to:
- Concatenate strings
- Change format based on conditions
- Convert types
- Transform into a structure that is easy for the template to consume
The benefits are clear.
- The view stays free of output‑only formatting code.
- The template can call a short tag when needed.
- Since the data is already in the template, the call is lightweight (no DB hit).
Example: moving complex processing from the template to a simple_tag
Using {% if %} and {% for %} in templates is natural. However, as conditions grow and nesting deepens, the template becomes a mix of markup and logic, hard to read.
For instance, when combining globally injected values (via a context processor) with request to build a user‑facing message, the following issues arise:
- Long conditional blocks
- Many lines for string concatenation and defaults
- Nested logic that obscures the flow
Extracting the logic into a simple_tag keeps the template focused on UI.
{% load ui_helpers %}
{% greeting_message %}
The heavy logic lives inside the tag; the template just calls it in one line. This keeps the template tidy and centralizes the logic for easier testing and maintenance.
Situations where simple_tag shines
Here are cases where I find simple_tag particularly useful:
- Repeated formatting across multiple templates – dates, amounts, labels, or phrases.
- UI state derived from request data – active menu based on the current path, toggle states from query strings.
- Transforming global context into a template‑friendly structure – turning a dict into a list of key/value pairs.
- UI helpers unrelated to the view’s main flow – determining CSS classes, generating badge text, simple permission flags.
The real takeaway: consistent team/project guidelines
My guidelines are not the ultimate answer, but having a rule set offers clear benefits:
- The rationale for each decision remains visible over time.
- Maintenance and debugging become easier.
- Code reviews encounter fewer disputes.
I am satisfied with this approach and continue to uphold it.
Final thoughts
What criteria do you use to split responsibilities?
- How much global context do you allow?
- How far do you push logic into template tags?
- Where do you draw the line for “what the view should do”?
Even if your criteria differ, having a clear set of rules is ultimately beneficial.
Related posts
There are no comments.