Skip to main content

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):

  1. .jinja-multilint.yaml or .jinja-multilint.yml
  2. .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 strings
  • TESTS — iterable of Jinja test name strings (used with is)
  • 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.

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-multilint and 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-lint for SLS structure, jinja-multilint for filter names, aristanetworks/j2lint for Jinja style.
  • You have an Ansible project but also custom Jinja2 templates outside Ansible: ansible-lint for the Ansible bits, jinja-multilint for the custom ones.

License

MPL 2.0

Metadata

Release files for jinja-multilint 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jinja-multilint 0.1.2
File Size Uploaded Provenance Actions
jinja_multilint-0.1.2.tar.gz 19.4 kB Without provenance Details for jinja_multilint-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for jinja-multilint 0.1.2
File Interpreter ABI Platform Actions
jinja_multilint-0.1.2-py3-none-any.whl Python 3 none any Details for jinja_multilint-0.1.2-py3-none-any.whl

Total release size: 39.5 kB

Release files / jinja_multilint-0.1.2.tar.gz

Download URL jinja_multilint-0.1.2.tar.gz
Size 19.4 kB
Tags Source
SHA-256 checksum
How to use checksums
30cd2c082ce937d793a8fb2d2c2d694c485bb7ae7dda660cde610c45dd9fecaa
BLAKE2b-256 checksum
How to use checksums
22b718cecd97d1fac0775470808bdc09e567f64662df617d8e194d6825fe84e1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / jinja_multilint-0.1.2-py3-none-any.whl

Download URL jinja_multilint-0.1.2-py3-none-any.whl
Size 20.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bd77f7f5100f1e95245ef231782b29557faaf18137aea54bf75e62f4c9ee551f
BLAKE2b-256 checksum
How to use checksums
5dfe7d6886472347ef17c8788927b55a39d028f83691f016b84f41ed638aee97
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.2.0 CPython/3.12.13

Security for jinja-multilint 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page