Djade
February 24, 2026 · View on GitHub
===== Djade
.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/djade/main.yml.svg?branch=main&style=for-the-badge :target: https://github.com/adamchainz/djade/actions?workflow=CI
.. image:: https://img.shields.io/pypi/v/djade.svg?style=for-the-badge :target: https://pypi.org/project/djade/
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge :target: https://github.com/pre-commit/pre-commit :alt: pre-commit
.. figure:: https://raw.githubusercontent.com/adamchainz/djade/main/logo.svg :alt: You can have any colour you like, as long as it’s jade.
..
A Django template formatter.
You can have any colour you like, as long as it’s [d]jade.
Djade formats Django template syntax with a style based on the template style guide <https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#template-style>__ in Django’s documentation.
It does not format HTML or other templated languages.
Djade is fast because it is built in Rust: benchmarked taking 20ms to format 377 templates.
Read more in the introductory post <https://adamj.eu/tech/2024/09/26/django-introducing-djade/>__, or below.
Improve your Django and Git skills with my books <https://adamj.eu/books/>__.
Installation
Use pip:
.. code-block:: sh
python -m pip install djade
Python 3.10 to 3.14 supported.
pre-commit hook
You can also install Djade as a pre-commit <https://pre-commit.com/>__ hook.
First, add the following to the repos section of your .pre-commit-config.yaml file (docs <https://pre-commit.com/#plugins>__):
.. code-block:: yaml
- repo: https://github.com/adamchainz/djade-pre-commit
rev: "" # Replace with the latest tag on GitHub
hooks:
- id: djade
Djade attempts to parse your current Django version from pyproject.toml.
If this doesn’t work for you, specify your target version with the --target-version option:
.. code-block:: diff
- id: djade
+ args: [--target-version, "5.2"] # Replace with Django version
The separate repository (djade-pre-commit) enables installation without compiling the Rust code.
The default configuration uses pre-commit’s |files option|__ to pick up all text files in directories called templates (source <https://github.com/adamchainz/djade-pre-commit/blob/main/.pre-commit-hooks.yaml>__).
You may wish to override this if you have templates in different directories by adding files to the hook configuration in your .pre-commit-config.yaml file.
.. |files option| replace:: files option
__ https://pre-commit.com/#creating-new-hooks
Second, format your entire project:
.. code-block:: sh
pre-commit run djade --all-files
Check these changes for any potential Djade bugs and commit them.
Try git diff --ignore-all-space to check non-whitespace changes.
Third, consider adding the previous commit SHA to a |.git-blame-ignore-revs file|__.
This will prevent the initial formatting commit from showing up in git blame.
.. |.git-blame-ignore-revs file| replace:: .git-blame-ignore-revs file
__ https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
Keep the hook installed to continue formatting your templates.
pre-commit’s autoupdate command will upgrade Djade so you can take advantage of future features.
Usage
djade is a command line tool that rewrites files in place.
Pass a list of template files to format them:
.. code-block:: console
$ djade templates/engine.html
1 file reformatted
Djade can also upgrade some old template syntax, up to a target Django version, which may be specified with the --target-version option.
When --target-version is not specified, Djade attempts to detect the target version from a pyproject.toml in the current directory.
If found, it attempts to parse your current minimum-supported Django version from |project.dependencies|__, supporting formats like django>=5.2,<6.0.
When available, it reports:
.. |project.dependencies| replace:: project.dependencies
__ https://packaging.python.org/en/latest/specifications/pyproject-toml/#dependencies-optional-dependencies
.. code-block:: sh
$ django-upgrade example.py
Detected Django version from pyproject.toml: 6.0
1 file reformatted
If this doesn’t work, no upgrade fixers are applied, unless you pass --target-version with a Django version formatted as <major>.<minor>:
.. code-block:: console
$ djade --target-version 6.0 templates/engine.html
1 file reformatted
Djade does not have any ability to recurse through directories. Use the pre-commit integration, globbing, or another technique to apply it to many files. For example, |with git ls-files pipe xargs|_:
.. |with git ls-files pipe xargs| replace:: with git ls-files | xargs
.. _with git ls-files pipe xargs: https://adamj.eu/tech/2022/03/09/how-to-run-a-command-on-many-files-in-your-git-repository/
.. code-block:: sh
git ls-files -z -- '*.html' | xargs -0r djade
…or PowerShell’s |ForEach-Object|__:
.. |ForEach-Object| replace:: ForEach-Object
__ https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object
.. code-block:: powershell
git ls-files -- '*.html' | %{djade $_}
The filename - makes Djade read from standard input and write to standard output.
In this case, Djade always exits with code 0, even if changes were made.
Options
--target-version
Optional: the version of Django to target, in the format <major>.<minor>.
If provided, Djade enables its fixers for versions up to and including the target version.
See the list of available versions with djade --help.
--check
Avoid writing any formatted files back. Instead, exit with a non-zero status code if any files would have been modified, and zero otherwise.
Formatting
Djade aims to format Django template syntax in a consistent, clean way.
It wants to be like Black <https://black.readthedocs.io/en/stable/>: opinionated and free of configuration.
Djade’s style is based on the rules listed in the Django contribution style guide’s template style section <https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#template-style>, plus some more.
Djade does not aim to format the host language of templates (HTML, etc.).
That is a much broader scope and hard to do without semantic changes.
For example, whitespace is significant in some HTML contexts, such as in <pre> tags, so even adjusting indentation can affect the meaning.
Below are the rules that Djade implements.
Rules from the Django style guide:
-
Single spaces at the start and end of variables and tags:
.. code-block:: diff
-{{train}} +{{ train }}
-{% blow whistle %} +{% blow whistle %}
-
Label
{% endblock %}tags that aren’t on the same line as their opening{% block %}tag:.. code-block:: diff
{% block funnel %} ... -{% endblock %} +{% endblock funnel %}
-
Sort libraries in
{% load %}tags:.. code-block:: diff
-{% load coal boiler %} +{% load boiler coal %}
-
Inside variables, no spaces around filters:
.. code-block:: diff
-{{ fire | stoke }} +{{ fire|stoke }}
-
Inside tags, single spaces between tokens:
.. code-block:: diff
-{% if locomotive == 'steam engine' %} +{% if locomotive == 'steam engine' %}
-
Unindent top-level
{% block %}and{% endblock %}tags when{% extends %}is used:.. code-block:: diff
-
{% extends 'engine.html' %} +{% extends 'engine.html' %}
-
{% block boiler %} +{% block boiler %} ...
-
{% endblock boiler %} +{% endblock boiler %}
-
Extra rules:
-
No leading empty lines:
.. code-block:: diff
{% extends 'engine.html' %} ...
-
No trailing empty lines:
.. code-block:: diff
... {% endblock wheels %}
-
Single spaces at the start and end of comments:
.. code-block:: diff
-{#choo choo#} +{# choo choo #}
-
Label
{% endpartialdef %}tags that aren’t on the same line as their opening{% partialdef %}tag:.. code-block:: diff
{% partialdef whistle %} ... -{% endpartialdef %} +{% endpartialdef whistle %}
-
No labels in
{% endblock %}tags on the same line as their opening{% block %}tag:.. code-block:: diff
-{% block funnel %}...{% endblock funnel %} +{% block funnel %}...{% endblock %}
-
No labels in
{% endpartialdef %}tags on the same line as their opening{% partialdef %}tag:.. code-block:: diff
-{% partialdef whistle %}...{% endpartialdef whistle %} +{% partialdef whistle %}...{% endpartialdef %}
-
Merge consecutive
{% load %}tags:.. code-block:: diff
-{% load boiler %}
-{% load coal %} +{% load boiler coal %}
-
Sort loaded items in
{% load ... from .. %}tags:
.. code-block:: diff
-{% load steam heat from boiler %}
+{% load heat steam from boiler %}
-
Unindent
{% extends %}tags:.. code-block:: diff
- {% extends 'engine.html' %} +{% extends 'engine.html' %}
-
Exactly one blank line between top-level
{% block %}and{% endblock %}tags when{% extends %}is used:
.. code-block:: diff
{% extends 'engine.html' %}
-
{% block funnel %}
...
{% endblock funnel %}
+
{% block boiler %}
...
{% endblock boiler %}
Fixers
Djade applies the below fixes based on the target Django version from --target-version.
Django 4.2+: length_is -> length
From the release note <https://docs.djangoproject.com/en/4.2/releases/4.2/#id1>__:
The ``length_is`` template filter is deprecated in favor of ``length`` and the ``==`` operator within an ``{% if %}`` tag.
Djade updates usage of the deprecated filter within if tags, without other conditions, appropriately:
.. code-block:: diff
-{% if engines|length_is:1 %}
+{% if engines|length == 1 %}
Django 4.1+: empty ID json_script fixer
From the release note <https://docs.djangoproject.com/en/4.1/releases/4.1/#templates>__:
The HTML ``<script>`` element ``id`` attribute is no longer required when wrapping the ``json_script`` template filter.
Djade removes the argument where json_script is passed an empty string, to avoid emitting id="":
.. code-block:: diff
-{% tracks|json_script:"" %}
+{% tracks|json_script %}
Django 3.1+: trans -> translate, blocktrans / endblocktrans -> blocktranslate / endblocktranslate
From the release note <https://docs.djangoproject.com/en/3.1/releases/3.1/#templates>__:
The renamed ``translate`` and ``blocktranslate`` template tags are introduced for internationalization in template code.
The older ``trans`` and ``blocktrans`` template tags aliases continue to work, and will be retained for the foreseeable future.
Djade updates the deprecated tags appropriately:
.. code-block:: diff
-{% load blocktrans trans from i18n %}
+{% load blocktranslate translate from i18n %}
-{% trans "Engine colours" %}
+{% translate "Engine colours" %}
-{% blocktrans with colour=engine.colour %}
+{% blocktranslate with colour=engine.colour %}
This engine is {{ colour }}.
-{% endblocktrans %}
+{% endblocktranslate %}
Django 3.1+: ifequal and ifnotequal -> if
From the release note <https://docs.djangoproject.com/en/3.1/releases/3.1/#id2:~:text=The%20%7B%25%20ifequal%20%25%7D%20and%20%7B%25%20ifnotequal%20%25%7D%20template%20tags>__:
The ``{% ifequal %}`` and ``{% ifnotequal %}`` template tags are deprecated in favor of ``{% if %}``.
Djade updates the deprecated tags appropriately:
.. code-block:: diff
-{% ifequal engine.colour 'blue' %}
+{% if engine.colour == 'blue' %}
Thomas!
-{% endifequal %}
+{% endif %}
-{% ifnotequal engine.colour 'blue' %}
+{% if engine.colour != 'blue' %}
Not Thomas.
-{% endifnotequal %}
+{% endif %}
Django 2.1+: admin_static and staticfiles -> static
From the release note <https://docs.djangoproject.com/en/2.1/releases/2.1/#features-deprecated-in-2-1>__:
``{% load staticfiles %}`` and ``{% load admin_static %}`` are deprecated in favor of ``{% load static %}``, which works the same.
Djade updates {% load %} tags appropriately:
.. code-block:: diff
-{% load staticfiles %}
+{% load static %}
-{% load admin_static %}
+{% load static %}
Django 1.3+: legacy variable assignment syntax
The minimum target Django version is 2.1, so this fixer is always active.
Django 1.3 added support for = to assign variables in {% with %} and {% blocktranslate %} tags.
Prior to this, they only supported the legacy syntax using the as keyword, which Django still supports.
Djade rewrites the older as syntax to the newer = one:
.. code-block:: diff
-{% with engines.count as total %}
+{% with total=engines.count %}
...
{% endwith %}
-{% blocktranslate with engine.colour as colour %}
+{% blocktranslate with colour=engine.colour %}
...
{% endblocktranslate %}