Jinja2 template linter with multi-ecosystem filter awareness (Salt, Ansible, Home Assistant)
Project description
jinja-multilint
A Jinja2 template linter with ecosystem-aware filter and test name validation.
Built-in support for Salt, Ansible, and Home Assistant. Extensible via plugins for any other Jinja2-based ecosystem, internal organization, or custom filter library.
Why
jinja-multilint fills a specific gap: lightweight, ecosystem-aware
validation of Jinja2 filter and test names, without requiring the full
runtime of the target ecosystem.
It is not a replacement for ecosystem-specialized linters like ansible-lint
or for full configuration validators like Home Assistant's check_config.
See Related tools for honest positioning of when to use
this versus alternatives.
Install
pip install jinja-multilint
Requires Python 3.10+.
Quick start
Lint a Salt template:
jinja-multilint --ecosystem salt template.sls
With a config file:
# .jinja-multilint.yaml
ecosystems:
- salt
filters:
- my_custom_filter
extensions:
- jinja2.ext.do
jinja-multilint template.sls
Modes
--mode strict # unknown filter/test → error (default)
--mode warn # unknown filter/test → stderr warning, exit 0
--mode lax # skip filter/test checks; only validate syntax
Syntax errors always fail regardless of mode.
Configuration
Auto-detected files in current working directory (first match wins):
.jinja-multilint.yamlor.jinja-multilint.yml.jinja-multilint-filters(flat list, one filter name per line; legacy compatibility for migrating from drm/jinja2-lint setups)
Override with --config PATH.
Full YAML schema:
mode: strict # strict | warn | lax (default: strict)
ecosystems: # built-in or third-party plugin names
- salt
- ansible
- my_org # third-party, see Plugins below
filters: # additional custom filter names
- my_filter_one
- my_filter_two
tests: # additional custom test names
- my_test_one
extensions: # Jinja2 extension import paths
- jinja2.ext.do
- jinja2.ext.loopcontrols
CLI flags extend (do not replace) config values.
CI/CD usage
GitLab CI
lint:jinja:
image: python:3.12
script:
- pip install jinja-multilint
- find . -type f -name "*.sls" -exec jinja-multilint --ecosystem salt {} +
Bitbucket Pipelines
- step:
name: 'Lint-Jinja'
image: python:3.12
script:
- pip install jinja-multilint
- find . -type f -name "*.sls" -exec jinja-multilint --ecosystem salt {} +
GitHub Actions
- name: Lint Jinja templates
run: |
pip install jinja-multilint
find . -type f -name "*.sls" -exec jinja-multilint --ecosystem salt {} +
With a third-party plugin from PyPI
script:
- pip install jinja-multilint jinja-multilint-my-org
- find . -type f -name "*.j2" -exec jinja-multilint --ecosystem my_org {} +
With an internal plugin from a private package registry
script:
- pip install --extra-index-url $PRIVATE_INDEX_URL jinja-multilint jinja-multilint-internal
- jinja-multilint --ecosystem salt --ecosystem internal templates/
With an in-repo plugin
script:
- pip install jinja-multilint
- pip install -e ./tooling/my-jinja-plugin
- jinja-multilint --ecosystem my_thing templates/
Pre-commit
# .pre-commit-config.yaml
- repo: local
hooks:
- id: jinja-multilint
name: Lint Jinja templates
entry: jinja-multilint --ecosystem salt
language: python
additional_dependencies: [jinja-multilint]
files: '\.(sls|jinja|j2)$'
Plugins
A plugin is a Python package that registers an ecosystem under the
jinja_multilint.ecosystems entry-point group. The plugin module exposes:
FILTERS— iterable of Jinja filter name stringsTESTS— iterable of Jinja test name strings (used withis)EXTENSIONS— iterable of Jinja2 extension import paths (optional)
Minimal plugin example
A plugin package called jinja-multilint-myorg:
jinja-multilint-myorg/
├── pyproject.toml
└── jml_myorg/
└── __init__.py
pyproject.toml:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "jinja-multilint-myorg"
version = "0.1.0"
[project.entry-points."jinja_multilint.ecosystems"]
myorg = "jml_myorg"
jml_myorg/__init__.py:
FILTERS = frozenset({
"myorg_custom_filter",
"myorg_another_filter",
})
TESTS = frozenset({
"myorg_test",
})
EXTENSIONS = [] # optional; defaults to empty
After pip install ./jinja-multilint-myorg, users reference the new ecosystem
by name (the entry-point key) in config or CLI:
ecosystems:
- myorg
Plugins can ship any subset of the three exports. A plugin that only defines
custom tests can omit FILTERS and EXTENSIONS.
What's in the box
| Plugin name | Source |
|---|---|
salt |
Salt's documented Jinja extensions |
ansible |
Ansible core filter/test set (no collections) |
home_assistant |
Home Assistant template filters/tests |
Scope
jinja-multilint validates:
- Template syntax (via Jinja2's parser)
- Filter and test names against the configured allow-list / ecosystem plugins
It does NOT validate:
- Filter or test argument count or names
- Filter behavior or return types
Schema-aware filter signature validation may be added as opt-in plugin metadata in a future release. The plugin API is forward-compatible with this change — existing plugins will continue to work.
Related tools
jinja-multilint occupies a specific niche. Other tools cover overlapping or
complementary ground; in many cases you should use those instead of (or in
addition to) this one.
Style linters (complementary; run alongside)
- aristanetworks/j2lint
(Apache 2.0): Jinja2 style and formatting rules — spaces around operators,
variable naming conventions, statement indentation. Complements
jinja-multilint: it checks how templates look, this checks whether filter and test names are valid. Both can run in the same CI pipeline.
Ecosystem-specialized linters / validators
When you're working exclusively within one ecosystem, the specialized tool is
usually more thorough than jinja-multilint for that ecosystem and should be
preferred (or used alongside).
| Tool | License | Covers | When to prefer it |
|---|---|---|---|
| ansible-lint | GPL-3.0+ | Ansible playbooks/roles/tasks — YAML, idioms, FQCN, module argument validation, and embedded Jinja2 with Ansible's filter set | Pure Ansible project. Strictly more thorough than us for Ansible content. |
| salt-lint | MIT | Salt state file YAML, common antipatterns, SLS structure | Salt projects — but salt-lint does not validate Jinja2 filter names, so run it alongside jinja-multilint, not instead. |
Home Assistant check_config |
Apache 2.0 | Full HA config validation including template rendering against live state | You have HA running locally and want true render-time validation. Not suitable for lightweight CI without an HA install. |
dbt parse / dbt compile |
Apache 2.0 | dbt model SQL + Jinja templates | Pure dbt project with dbt installed and a profile configured. |
| drm/jinja2-lint | DBAD | Generic Jinja2 syntax + user-supplied filter list | The original syntax checker that inspired this project. DBAD license makes it awkward for downstream redistribution; no ecosystem knowledge built-in. |
Decision guide
Use a specialized linter when:
- Your repo targets one ecosystem (just Ansible, just HA, just dbt).
- You need ecosystem-specific rules beyond filter name validation (idiomatic patterns, module argument checks, naming conventions).
- The specialized tool can run in your environment without overhead concerns.
Use jinja-multilint when:
- You work in Salt — salt-lint doesn't validate Jinja2 filter names, and this tool fills that gap.
- Your repo spans multiple ecosystems (e.g., Salt states plus some Ansible plus custom Jinja2 templates).
- You need lightweight CI checks without pulling in the full Ansible / HA /
dbt runtime —
pip install jinja-multilintand you're done. - You have organization-specific custom filters and want allow-list validation without the heavyweight ecosystem tools' opinionated overhead.
- You need to write a plugin for a custom Jinja2 application that isn't covered by any specialized tool.
Use them together when:
- You have a Salt project:
salt-lintfor SLS structure,jinja-multilintfor filter names,aristanetworks/j2lintfor Jinja style. - You have an Ansible project but also custom Jinja2 templates outside
Ansible:
ansible-lintfor the Ansible bits,jinja-multilintfor the custom ones.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jinja_multilint-0.1.2.tar.gz.
File metadata
- Download URL: jinja_multilint-0.1.2.tar.gz
- Upload date:
- Size: 19.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30cd2c082ce937d793a8fb2d2c2d694c485bb7ae7dda660cde610c45dd9fecaa
|
|
| MD5 |
a6ac378a0d7207ed0f3ae4380c97b577
|
|
| BLAKE2b-256 |
22b718cecd97d1fac0775470808bdc09e567f64662df617d8e194d6825fe84e1
|
File details
Details for the file jinja_multilint-0.1.2-py3-none-any.whl.
File metadata
- Download URL: jinja_multilint-0.1.2-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd77f7f5100f1e95245ef231782b29557faaf18137aea54bf75e62f4c9ee551f
|
|
| MD5 |
962b393c6c9160d0ccbcf9ccefc886e7
|
|
| BLAKE2b-256 |
5dfe7d6886472347ef17c8788927b55a39d028f83691f016b84f41ed638aee97
|