Skip to main content

jinja-lsp

CI Release

Language server for Jinja templates — diagnostics, navigation, completions, hover, and Jinja-aware formatting. One Rust binary, any LSP-capable editor. Static analysis only — it never imports, renders, or executes your templates or host Python.

It runs alongside your Python and HTML language servers, owning the Jinja layer end to end and staying silent everywhere else.

Features

Diagnostics 21 checks — undefined variables/filters/functions/tests, unused macros/imports, duplicate & shadowed bindings, inheritance errors, wrong call args, missing templates; inline noqa suppression
Navigation go-to-definition (macros, blocks, templates, imports), find references, document & call hierarchy
Hover built-in docs for filters/tests/functions, macro signatures, variable scope and definition site
Completions variables, attributes, filters, tests, statement keywords, template paths, imported macro names
Signature help macro and filter call signatures, with the active argument highlighted
Symbols & lenses document symbols, semantic tokens, folding, inlay hints, reference/override code lenses
Code actions quick-fixes from the diagnostic catalog, extract-to-macro, wrap-in-block/if/for, and rename
Formatting Jinja-aware formatting of the template layer — jinja-lsp format
check CLI the same diagnostics as a linter — jinja-lsp check . with rich / compact / json output

Installation

curl -fsSL https://raw.githubusercontent.com/alex-oleshkevich/jinja-lsp/master/install.sh | bash

This picks the right build for your machine, checks it against the published SHA-256, and puts the binary in ~/.local/bin. It never asks for sudo and writes nothing outside that directory. If ~/.local/bin is not on your PATH, the script says so and prints the line to add.

Two environment variables change what it does. Note that they go on bash, not on curl, because the two are separate processes and only bash runs the script:

# pin a version instead of taking the latest
curl -fsSL https://raw.githubusercontent.com/alex-oleshkevich/jinja-lsp/master/install.sh \
  | JINJA_LSP_VERSION=0.2.0 bash

# install somewhere other than ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/alex-oleshkevich/jinja-lsp/master/install.sh \
  | JINJA_LSP_INSTALL_DIR=~/bin bash

Intel Macs have no published build. Apple Silicon, Linux (x86_64 and ARM64), and Windows do.

If you would rather use a package manager:

uv tool install jinja-lsp
pip install jinja-lsp
yay -S jinja-lsp-plus-bin          # Arch Linux

The Python packages ship the same self-contained Rust binary, so neither needs a Rust toolchain, and nothing imports Python at runtime. On the AUR the package is jinja-lsp-plus-bin. The similarly named jinja-lsp-bin belongs to an unrelated project.

You can also grab an archive from the releases page and put the binary wherever you like.

Editor setup

The server is launched as a subprocess and speaks LSP over stdio (jinja-lsp lsp). There is no TCP/socket transport.

Neovim

Using nvim-lspconfig — paste this into ~/.config/nvim/init.lua:

local lspconfig = require("lspconfig")
local configs   = require("lspconfig.configs")

if not configs.jinja_lsp then
  configs.jinja_lsp = {
    default_config = {
      cmd       = { "jinja-lsp", "lsp" },
      filetypes = { "jinja", "jinja.html", "htmldjango" },
      root_dir  = lspconfig.util.root_pattern("jinja.toml", "pyproject.toml", ".git"),
      -- mirrors jinja.toml; all keys optional — overlay on top of any discovered config file
      init_options = {
        templates = { "templates", "..." },
        extras    = {},
        hints     = {},
        lint      = { select = {}, ignore = {} },
      },
    },
  }
end

lspconfig.jinja_lsp.setup({})

Neovim 0.11+: you can also use the built-in vim.lsp.config API instead:

vim.lsp.config('jinja_lsp', {
  cmd = { 'jinja-lsp', 'lsp' },
  filetypes = { 'jinja', 'jinja.html', 'htmldjango' },
  root_markers = { 'jinja.toml', 'pyproject.toml', '.git' },
})
vim.lsp.enable('jinja_lsp')

Helix

# ~/.config/helix/languages.toml
[language-server.jinja-lsp]
command = "jinja-lsp"
args = ["lsp"]

[[language]]
name = "jinja"
language-servers = ["jinja-lsp"]

[[language]]
name = "html"
language-servers = ["jinja-lsp"]

Zed

Install from the Zed extensions panel (Cmd+Shift+X) — search for Jinja Plus and click Install (extension id jinja-plusjinja-lsp was already taken on Zed's marketplace). It activates automatically for Jinja and HTML templates.

To control server order alongside other language servers or pass initialization options, add to ~/.config/zed/settings.json (the language-server id is jinja2-lsp and the language is Jinja2 (HTML)):

{
  "languages": { "Jinja2 (HTML)": { "language_servers": ["jinja2-lsp"] } },
  "lsp": { "jinja2-lsp": { "initialization_options": { "templates": ["templates"], "extras": ["starlette"] } } }
}

Configuration

Most projects need no configuration. Template directories are found automatically by looking for templates/, <project-name>/templates/, jinja/, and j2/.

When you do want to configure something, the server walks up from the project root and takes the first of these it finds:

  1. jinja.toml
  2. [tool.jinja] in pyproject.toml

Your editor's initializationOptions are layered on top of whichever it found. The overlay replaces only the keys it sets, so an editor setting for extras will not wipe out the templates list in your jinja.toml. Clearing a setting in your editor falls back to the file value rather than leaving the old override in place. Both files are watched, so edits apply without restarting the server.

General options

Option Default
templates (auto-discovered) template root directories. "..." expands to the auto-discovered set, so you can add a directory without losing the defaults
extensions ["html", "jinja", "jinja2", "j2"] file extensions to scan
extras [] framework packs that teach the server about globals your framework injects: flask, starlette, starlette-babel, starlette-flash
hints [] directories of hint files describing your own context variables and macros
custom_builtins [] directories of *.md docs for third-party filters, functions, and tests
inline_patterns ["render_template_string"] host function names whose string argument is parsed as an inline template
lint.select (all) diagnostic codes or classes to enable, such as JINJA-E1 or JINJA-W
lint.ignore [] diagnostic codes or classes to suppress
# jinja.toml
templates = ["templates", "..."]
extras = ["starlette"]

[lint]
ignore = ["JINJA-W106"]

To suppress a finding in one place rather than project-wide, use a comment in the template: {# noqa #} for that line, {# noqa: JINJA-W201 #} for one code, or {# noqa-file #} for the whole file.

Formatter options

These live under [format] and apply to both jinja-lsp format and formatting from your editor. The formatter only rewrites what is inside Jinja delimiters, so your HTML, YAML, or whatever else the file contains is reproduced byte for byte.

Option Default
indent_size 4 spaces per indent level, ignored when use_tabs is set
use_tabs false indent with tabs instead of spaces
space_around_pipe true x | upper rather than x|upper
space_around_operators false a + b rather than a+b, for symbolic operators only
space_after_comma true truncate(20, true) rather than truncate(20,true), in filter-call arguments
space_inside_parens false truncate( 20, true ) rather than truncate(20, true), in filter-call arguments
space_inside_variable_delimiters true {{ x }} rather than {{x}}
space_inside_block_delimiters true {% if x %} rather than {%if x%}
blank_lines_after_block 0 blank lines to leave after a top-level {% endblock %}, {% endfor %}, and so on
trim_blocks false drop the first newline after a {% %} tag, matching Jinja2's runtime option of the same name
lstrip_blocks false drop leading whitespace before a {% %} tag, likewise
preferred_quote "preserve" "single" or "double" to normalize string literals, "preserve" to leave them alone
newline_at_eof true end the file with exactly one newline
trim_trailing_whitespace true strip trailing whitespace from every line

Two things the formatter deliberately leaves alone. Keyword operators (and, or, is, in, not) always keep their surrounding spaces, whatever space_around_operators says, because removing them would change how the expression tokenizes: andb is one identifier, not and followed by b. And the comma and paren options above cover filter-call arguments only. Commas you wrote in a macro call, a dict, or a list stay exactly as you typed them, so {{ post_url(post,absolute=true) }} is left untouched.

# jinja.toml
[format]
indent_size = 2
space_around_pipe = false
preferred_quote = "double"

CLI

jinja-lsp lsp                                              # run the language server over stdio
jinja-lsp check PATH [--select CODES] [--ignore CODES] [--format rich|compact|json]
jinja-lsp format PATH [--check]
jinja-lsp doctor [--config PATH]                           # report what it discovers here

doctor answers "why is it not seeing my templates". It prints the config file it found (or that it fell back to zero-config), each template directory with how many files matched, the builtin sources that loaded and what each contributed, and any *.hints.md sidecars. It reports directories you configured but that do not exist, which the indexer skips silently, and exits 1 when it finds a problem.

check's json output matches the format the test suite asserts against, so it diffs cleanly in CI. format rewrites the Jinja layer only and is round-trip safe.

Development

Every routine task has a just recipe — run just to list them.

just build
just test        # cargo nextest run
just test-e2e    # Python LSP-protocol suite against the real binary
just check-all   # everything CI gates on

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

jinja_lsp-0.4.2-py3-none-win_amd64.whl (3.1 MB view details)

Uploaded Python 3Windows x86-64

jinja_lsp-0.4.2-py3-none-manylinux_2_28_x86_64.whl (3.2 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

jinja_lsp-0.4.2-py3-none-manylinux_2_28_aarch64.whl (3.0 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

jinja_lsp-0.4.2-py3-none-macosx_11_0_arm64.whl (3.0 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file jinja_lsp-0.4.2-py3-none-win_amd64.whl.

File metadata

  • Download URL: jinja_lsp-0.4.2-py3-none-win_amd64.whl
  • Upload date:
  • Size: 3.1 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for jinja_lsp-0.4.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 59f0f1361bd979f4383111e41f3d7de5e5c5760aacf63bbfbfe1a5f22f5b98ae
MD5 0f4ee0e7df1932c7387c059b16db4daa
BLAKE2b-256 c1552fc0d7286fe2f5b091642723c7e8dee02689339f71546fb9665ef919898e

See more details on using hashes here.

Provenance

The following attestation bundles were made for jinja_lsp-0.4.2-py3-none-win_amd64.whl:

Publisher: release.yml on alex-oleshkevich/jinja-lsp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jinja_lsp-0.4.2-py3-none-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for jinja_lsp-0.4.2-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e5e3d08d1adcedf6e98aae2f02761bf7de1914fa13c6f7132133aac6d1a5883a
MD5 201de39d353ec7f423be78c05a9cceea
BLAKE2b-256 fd8dcde77b74d682977ad9a1887b120fca84fbb82b81bf3d607aa86d6189abaf

See more details on using hashes here.

Provenance

The following attestation bundles were made for jinja_lsp-0.4.2-py3-none-manylinux_2_28_x86_64.whl:

Publisher: release.yml on alex-oleshkevich/jinja-lsp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jinja_lsp-0.4.2-py3-none-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for jinja_lsp-0.4.2-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 f79b1bc66fa4c12574e8aa07a1610a7ea98ef2c2ad3538656cad5b81f725ccca
MD5 8844ad77123f88ed5a2bd1fbffa6fff9
BLAKE2b-256 dca5add79f8f2a3b2b113187281b3b2d69d4c68b1b745c5d04ba29d8499f9de5

See more details on using hashes here.

Provenance

The following attestation bundles were made for jinja_lsp-0.4.2-py3-none-manylinux_2_28_aarch64.whl:

Publisher: release.yml on alex-oleshkevich/jinja-lsp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jinja_lsp-0.4.2-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jinja_lsp-0.4.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 51d290217022e9d49741cb31060e76c33048bf9f5303e0887229d888af1fd28c
MD5 60c420cca2096189de6220e3c0681746
BLAKE2b-256 bba0866e2a044a37f5ed342b729629178a14203b8ba1e8dda7df2f0b030d39e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for jinja_lsp-0.4.2-py3-none-macosx_11_0_arm64.whl:

Publisher: release.yml on alex-oleshkevich/jinja-lsp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.2 This release

4 files

0.4.1

4 files

0.4.0

4 files

0.3.0

4 files

0.2.0

4 files

0.1.0

4 files

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