Library / SDK
rtts/djhtml avatar
rtts/djhtml

djhtml indents Django and Jinja templates without reformatting them

Django/Jinja template indenter

642 stars37 forksPythonGPL-3.0

At a glance

What is it?
djhtml is a dependency-free Python tool that adds and removes leading whitespace in mixed HTML, CSS and JavaScript templates containing Django or Jinja tags. It is an indenter rather than a formatter, which is a distinction that decides whether you will like it.
Who is it for?
Adopt djhtml if your templates are already well structured and only the indentation drifts, and you want a zero-dependency tool that can run in pre-commit across HTML, CSS and JavaScript files. Do not adopt it expecting a formatter, since it will not insert newlines or fix broken structure, and look at djangofmt if you need a real formatter.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

An indenter, not a formatter, and that is the whole design

The distinction the project leads with is the one you need to evaluate it on. DjHTML is an indenter and not a formatter: it will only add or remove whitespace at the beginning of lines, and it will not insert newlines or other characters. The stated goal is to correctly indent already well-structured templates, not to fix broken ones. That is a narrow promise, and it is narrow for good reason. A formatter that reflows a template has to decide where line breaks go, and in a template with inline filters, conditionals inside attributes and human formatted block content, those decisions are opinions. An indenter only answers one question, which is at what column should this line start. If your templates are messy because people wrote them by hand over years, djhtml will not rescue them, and if your templates are messy because a formatter upstream mangled them, djhtml will not restore them. If your problem is that the indentation of an otherwise clean template keeps drifting because different people have different tab widths in their editors, then this is exactly the tool for you.

Tokenizing the template, not the HTML

The work happens in the tokenization step, which the project lets you inspect directly. Running djhtml with the -d or --debug argument shows a Python representation of the tokens that are created, which is a genuinely useful affordance for a tool whose job is to guess the structure of something that is not quite HTML. The tool has to understand three grammars at once: HTML tags, which nest, and the template languages, Django and Jinja, which have their own block and tag vocabulary. That is where the difficulty lies. An HTML tag such as a blockquote opens a scope that closes at the matching close tag, and a template tag such as a block opens a scope that closes at endblock, and the two have to be interleaved correctly for the indentation to come out right. A template where a template block sits inside an HTML attribute, or an HTML tag straddles a template conditional, is exactly the case where a naive implementation goes wrong. The repository layout is small and conventional, with a djhtml/ package directory, a tests/ directory, a noxfile.py, pyproject.toml, setup.py, setup.cfg, and the .pre-commit-hooks.yaml that defines the hook entries the pre-commit framework consumes. Installation is a plain pip install, and the tool is then run against a file or a directory:

bash
    $ pip install djhtml
bash
    $ djhtml template.html
    reindented template.html
    1 template has been reindented.

Three commands, three modes, one underlying indenter

The indenter operates in one of three modes, and the mode boundaries follow the file format rather than any user choice in the common case. DjHTML mode is the default, invoked by the djhtml command or the pre-commit hook. DjCSS mode is entered when a style tag is encountered in DjHTML mode, and it can also be invoked directly with the djcss command. DjJS mode is entered when a script tag is encountered, and has its own djjs command. The reason to separate them is that CSS and JavaScript have their own block structures, and running HTML indentation rules over a script block would indent it as if it were markup. Because the modes are entered automatically when a style or script tag is seen, the common case needs no flags at all, and the separate commands exist for the case where you have standalone CSS or JavaScript files. That split is also why the pre-commit configuration registers three hooks rather than one, since the pre-commit framework matches files by type and pattern before the tool ever runs.

The check mode is what makes this safe to enforce in CI

The exit status behaviour is what turns a formatter into something a team can enforce. An exit status of 0 means everything went well, regardless of whether any files were changed, so a run that modified nothing and a run that modified everything both succeed. When the -c or --check option is used, the exit status is 1 when one or more files would have changed, but no changes are actually made. That is a dry run, and it is the mode you want in CI. The other command-line arguments are few. There is -h for help text, -v for the version number, -t or --tabwidth N to set the tab width, where the default is to guess, and -b or --extra-block BEGIN,END to define an extra non-standard block tag, which can be used multiple times. The last of those is the escape hatch for custom template syntax that the tool does not know about, and it is worth knowing it exists before you assume a proprietary tag will break the run. The full list is given by djhtml -h.

fmt:off regions and the content that is skipped by default

There are two ways to protect content from being reindented. The first is the explicit marker, using the fmt:off and fmt:on operators, which exclude the specific lines between them. The second is that certain regions are ignored depending on the current mode: contents inside pre tags, HTML comments, C style comments, and comment tags. The first two are obvious, since preformatted text and comments carry their own layout, and the C style comment matters because a JavaScript block comment usually contains a table or a diagram aligned by hand. The comment tag is the one to check in your own templates, since translated text inside a comment block can have arbitrary leading whitespace that a team may have aligned deliberately for translators. The default behaviour, ignoring these regions, is the right default. The failure mode to watch for is the opposite case, where a template is structured in a way the tokenizer does not expect and the tool indents it consistently but wrongly. The project's own response to that is described in the pre-commit section of the documentation, which asks for an issue with the relevant part of the input file and an example of how it should have been formatted.

The pre-commit configuration and the commit that fails

The recommended way to use djhtml is as a pre-commit hook, so HTML, CSS and JavaScript files are indented automatically on every commit. The configuration is a standard pre-commit block, with the repository, the rev and the three hook ids. The rev is set to main with a comment to replace it with the latest tag, and the documentation then explains that pre-commit autoupdate handles that replacement, which is the recommended practice. The hooks can be customised in two ways. To override a command-line option you change the entry point of the hook, for example djhtml --tabwidth 2 for HTML files. To limit which files a hook operates on you use the pre-commit filtering mechanisms, and the examples show restricting djhtml to HTML files in template directories with a files pattern, running djcss only on SCSS files with types, and excluding JavaScript files in vendor directories. What a commit looks like when the hook changes a file is also documented, with the hook id, the note that files were modified by this hook, and the two ways forward: run SKIP=djhtml git commit to commit anyway, or open an issue with the offending input. The SKIP variable is the standard pre-commit escape hatch and it is the right thing to reach for when the tool is wrong about your template.

What djhtml will not do, and what to use instead

The main limitation is the one the project names itself. If you are looking for an actual template formatter, the documentation points at the djangofmt project instead, and that is the honest advice, because djhtml will not insert newlines, will not normalise quoting or attribute order, and will not rescue a template whose structure is wrong. The second limitation is platform encoding. DjHTML requires Python 3.9 or higher and is compatible with all operating systems supported by Python, but Windows still uses legacy code pages instead of UTF-8, so the documentation highly advises setting the environment variable PYTHONUTF8 to 1 with the setx command, which sets it machine wide on Windows. The third is the reach of the pattern catalogue. DjHTML understands Django and Jinja tags, and anything outside that vocabulary needs the extra block option or a fmt:off region. There is also a mundane but real cost: the project is GPL-3.0, so if you want to vendor its logic into a proprietary tool rather than run it as a command or a pre-commit hook, that is a different conversation. Run as a binary or a hook, it costs nothing beyond the licence of the tool itself, and it has no runtime dependencies, which is the other reason it installs cleanly in a minimal CI image.

Editorial conclusion

Adopt djhtml if your templates are already well structured and only the indentation drifts, and you want a zero-dependency tool that can run in pre-commit across HTML, CSS and JavaScript files. Do not adopt it expecting a formatter, since it will not insert newlines or fix broken structure, and look at djangofmt if you need a real formatter. Before you wire it in, run djhtml -c over your existing templates to see which files it would rewrite, and check whether the templates that matter to you contain content the indenter ignores, such as pre blocks, HTML comments, C style comments and comment tags.

Frequently asked questions

What does djhtml do to my Django or Jinja templates?

It is an indenter and not a formatter. It only adds or removes whitespace at the beginning of lines and will not insert newlines or other characters, so the goal is to correctly indent already well-structured templates rather than to fix broken ones.

How do I install and run djhtml?

Install it with `pip install djhtml`, then run `djhtml template.html` to reindent a file, or `djhtml .` to indent all HTML files under the current directory. It requires Python 3.9 or higher and has no dependencies.

What is the difference between djhtml, djcss and djjs?

They are the three modes of the same indenter. DjHTML mode is the default and is entered by the djhtml command or the pre-commit hook, DjCSS mode is entered when a style tag is encountered and can be run directly as djcss, and DjJS mode is entered on a script tag and can be run as djjs.

How do I stop djhtml from reindenting part of a template?

Use the fmt:off and fmt:on operators around the lines you want excluded. Contents inside pre tags, HTML comments, C style comments and comment tags are also ignored, depending on the current mode.

Can djhtml be used as a pre-commit hook?

Yes. Install pre-commit, add the djhtml, djcss and djjs hook ids to your .pre-commit-config.yaml, and run pre-commit autoupdate to pin main to the latest tag. Command-line options can be overridden through the hook entry, and the files each hook touches can be limited with the standard pre-commit filters.

Official sources

  1. Issues
  2. License: GPL-3.0
  3. README
  4. Releases
  5. rtts/djhtml on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rtts-djhtml.svg)](https://hysenlabs.com/projects/rtts-djhtml)

Was this useful?