neovim-treesitter/nvim-treesitter

[DEPRECATED] Distributed-maintenance fork — please use nvim-treesitter/nvim-treesitter (actively maintained)

Tree-sitter Query

142

5,821 commits

updated Sep 21, 2026

See the code

README

nvim-treesitter

[!WARNING] This project is deprecated. Please use nvim-treesitter/nvim-treesitter — see Migrating to upstream below.

Status

This fork was born from a gap, and is closing because the gap closed.

The gap. Upstream nvim-treesitter went dormant and its repository was archived after years in which nearly all maintenance had serialized on a very small team. This fork was an attempt to keep the project alive by removing that single point of failure: instead of one monolith owning every language's queries, responsibility would be distributed to the people closest to each language — grammar owners and heavy users — backed by per-language query repos (nvim-treesitter-queries-<lang>), a shared parser registry for versioning, and CI validating every query change against its parser.

Finding 1: the gap closed. Upstream was resurrected by its original maintainers and is actively developed again, carrying the same main-branch rewrite this fork was based on. When the canonical project is healthy, a parallel universe of ~330 per-language repos is more coordination than value.

Finding 2: distributed maintenance only partly landed. Some query repos found maintainers; most didn't. This turned out to be structural, not a recruiting problem: a grammar is written once, but its queries are perpetual, editor-specific upkeep, so grammar owners rarely want the second job even when the infrastructure is handed to them. Upstream independently reached the same conclusion a few months later, dropping its own query-maintainer roster with the note that "most maintainers stopped being responsive very quickly." Two systems, one lesson: distributing the structure of responsibility doesn't distribute the labor.

What lives on: the zsh grammar maintained by this fork's author was upstreamed and is now upstream's parser for zsh; the query-validation CI setup is worth borrowing; and the registry experiment remains preserved below and across the org for anyone who wants to pick it up.

Thank you to everyone who starred, filed issues, adopted a query repo, or contributed along the way. Please point your configs at upstream — the migration notes cover the one rough edge (old copied query directories need resetting).

Migrating to upstream

Upstream installs queries as symlinks into its own runtime; this fork copied them into ~/.local/share/nvim/site/queries/<lang>/ as real directories, which upstream's installer cannot replace — you will see EEXIST: file already exists errors from :TSUpdate until you reset them:

rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/queries

Then reinstall with the bang (plain :TSInstall / :TSUpdate skips already-installed parsers without relinking queries):

:TSInstall! <your languages>

[!CAUTION] This is a full, incompatible rewrite. Treat it as a new plugin and set it up from scratch following the instructions below. If you need the previous version, use the master branch (locked, no new features).


Requirements

RequirementVersion
Neovim0.10.0 or later
treesitter-parser-registrylatest
tree-sitter CLI0.26.1 or later — install via your system package manager, not npm
C compilersee cc requirements
curlany recent version (used for HTTP downloads)

[!IMPORTANT] Neovim support tracks the latest stable release and the latest nightly prerelease only. Other versions may work but are not tested.


Installation

lazy.nvim

{
  'nvim-treesitter/nvim-treesitter',
  dependencies = { 'neovim-treesitter/treesitter-parser-registry' },
  lazy = false,
  build = ':TSUpdate',
}

[!IMPORTANT] This plugin does not support lazy-loading.

Other plugin managers

Add :TSUpdate as a post-install / post-update build step.


Quick start

You do not need to call setup unless you want to change the install directory.

-- optional — only needed to override the default install_dir
require('nvim-treesitter').setup {
  -- parsers and queries are installed here (prepended to runtimepath)
  install_dir = vim.fn.stdpath('data') .. '/site',
}

Install parsers and their queries:

require('nvim-treesitter').install { 'rust', 'python', 'typescript' }

Then enable features per language. Features are not enabled automatically.

vim.api.nvim_create_autocmd('FileType', {
  pattern = { 'rust', 'python', 'typescript' },
  callback = function()
    vim.treesitter.start()                                    -- highlighting
    vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'     -- folds
    vim.wo.foldmethod = 'expr'
    vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" -- indentation
  end,
})

Commands

CommandDescription
:TSInstall {lang...}Install parsers and queries. No-op if already installed.
:TSInstall! {lang...}Force reinstall (useful after upgrading the plugin).
:TSUpdate [{lang...}]Update installed parsers and queries to latest release. Omit languages to update all.
:TSUpdate! [{lang...}]Update, bypassing the per-parser version cache (24h TTL).
:TSUninstall {lang...}Remove parsers and queries for the specified languages.
:TSRegistryUpdateForce-refresh the registry from GitHub, bypassing the 7-day cache.
:TSStatusOpen a status buffer showing installed version vs. latest for each language.
:TSLogShow log from the last install/update/uninstall run.

Supported languages and features

Languages are discovered from the neovim-treesitter registry. Any language in the registry can be installed; there are no tiers.

Registry and version caching

Two caches avoid unnecessary network requests:

  1. Registry cache (7-day TTL) — the full registry.json is fetched from GitHub on first use and stored at <stdpath('data')>/site/registry/. Subsequent loads within 7 days use the cached copy. If a fetch fails, the stale cache is used as a fallback.

  2. Per-parser version cache (24-hour TTL) — for each installed language, the latest known parser and query versions are cached so :TSUpdate doesn't hit the network for every language on every run.

To refresh each cache independently:

" Force re-fetch the registry (e.g. after a new language is added)
:TSRegistryUpdate

" Force re-check per-parser versions (bypasses the 24h cache)
:TSUpdate!

What is installed

For each language, nvim-treesitter installs:

  • Parser — a compiled .so fetched from the upstream grammar repository
  • Queries.scm files sourced from whichever location the registry designates:
    • A community query repo (nvim-treesitter-queries-<lang> under the neovim-treesitter GitHub org) for external_queries languages
    • The parser repo itself for self_contained languages (where the parser author ships Neovim queries alongside the grammar)

The source type is transparent to users — :TSInstall handles both.

Supported query types

Query fileFeatureHow to enable
highlights.scmSyntax highlightingvim.treesitter.start()
injections.scmMulti-language documentsautomatic after start()
folds.scmTreesitter-based foldsvim.wo.foldmethod = 'expr'
indents.scmTreesitter-based indentationvim.bo.indentexpr = ...
locals.scmScope/definition lookupused by other plugins

Local overrides

Override queries for a language

Query files in your Neovim config's runtimepath take precedence over installed queries. Place a file at:

~/.config/nvim/queries/<lang>/<type>.scm

To extend (not replace) the installed queries, add this as the first line:

; extends

To replace them entirely, omit that line.

See :h treesitter-query-modelines for details.

Use a local parser checkout

Point install_dir at a directory you manage, then place your compiled parser and queries there directly:

<install_dir>/
  parser/
    <lang>.so          ← compiled parser binary
  queries/
    <lang>/
      highlights.scm   ← query files
require('nvim-treesitter').setup {
  install_dir = '/path/to/my/parsers',
}

Neovim will use parsers and queries from install_dir as long as it is on runtimepath, which setup ensures. You can still use :TSInstall for other languages alongside your local overrides.


Local parsers

To install a parser that is not in the registry, or to use your own fork of a parser, add it to local_parsers in setup(). Each value is a registry entry — the same shape used in registry.json — with a source field:

-- Local directory checkout (no network fetch)
require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'local',
        path         = '~/Development/tree-sitter-zsh',
        queries_path = 'nvim-queries/zsh',  -- subdir containing .scm files
      },
      filetypes = { 'zsh' },
    },
  },
}

-- Remote URL not in the registry (self_contained = ships its own queries)
require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'self_contained',
        url          = 'https://github.com/georgeharker/tree-sitter-zsh',
        semver       = false,
        queries_path = 'nvim-queries/zsh',
      },
      filetypes = { 'zsh' },
    },
  },
}

Then: :TSInstall zsh

If Neovim does not detect your language's filetype by default, register the parser name manually:

vim.treesitter.language.register('mylang', { 'ml' })

Contributing

Fixing or improving queries for an existing language

Each language's queries live in their own repository under the neovim-treesitter GitHub org:

https://github.com/neovim-treesitter/nvim-treesitter-queries-<lang>

Open a pull request there. CI will validate your changes automatically. See the contributing guide for the full workflow.

Adding a new language

  1. Open a pull request adding an entry to registry.json in the registry repo
  2. Create the nvim-treesitter-queries-<lang> repository following the query repo setup guide

Alternatively, if you maintain the parser itself you can ship queries directly from your parser repo using the self-contained model — see the self-contained migration guide.

Claiming maintainership of a language repo

Add yourself to the CODEOWNERS file in the language's query repo and open a PR. See the governance guide for details.


Migrating from the previous version

The previous version of nvim-treesitter pinned specific parser revisions inside the plugin and used a tiered system (stable/unstable/unmaintained). The new version:

  • Discovers languages and their latest versions from the community registry
  • Fetches queries from per-language repos maintained by language communities
  • Has no tiers — if a language is in the registry it can be installed
  • No longer uses parsers.lua — per-language config via parsers.<lang> = {} is not supported in this version
  • Removes :TSInstallFromGrammar — parser generation from grammar source is not part of the installer's scope

Plugin manager build step

Replace any reference to tiers in your build step:

-- before (no longer valid)
build = ':TSUpdate stable'

-- after
build = ':TSUpdate'

Custom parser entries

The old pattern using a User TSUpdate autocmd no longer works:

-- OLD — no longer supported
vim.api.nvim_create_autocmd('User', {
  pattern = 'TSUpdate',
  callback = function()
    require('nvim-treesitter.parsers').zsh = {
      install_info = {
        url = 'https://github.com/georgeharker/tree-sitter-zsh',
        queries = 'nvim-queries/zsh',
      },
      tier = 1,
    }
  end,
})

Use local_parsers in setup() instead:

-- NEW — remote URL (self_contained: ships its own queries)
require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'self_contained',
        url          = 'https://github.com/georgeharker/tree-sitter-zsh',
        queries_path = 'nvim-queries/zsh',
      },
      filetypes = { 'zsh' },
    },
  },
}

For a local directory checkout instead of a remote URL:

require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'local',
        path         = '~/Development/tree-sitter-zsh',
        queries_path = 'nvim-queries/zsh',
      },
      filetypes = { 'zsh' },
    },
  },
}

Then install as normal: :TSInstall zsh.

Old fieldNew fieldNotes
url = '...'source.url = '...'moved under source
path = '...'source.path = '...'moved under source, implies type = 'local'
location = '...'source.location = '...'monorepo subdir, moved under source
queries = 'subdir'source.queries_path = 'subdir'renamed and moved under source
semver = falsesource.semver = falsemoved under source
revision / min_version(removed)version managed by the registry
tier(removed)no tiers in new system
generate / generate_from_json(removed)parser generation not supported

Health check

Run :checkhealth nvim-treesitter to verify the installation.

Contributors

(top 30 of 373)

web-flow

878 commits

theHamsta

680 commits

clason

494 commits

neovim-treesitter/nvim-treesitter

[DEPRECATED] Distributed-maintenance fork — please use nvim-treesitter/nvim-treesitter (actively maintained)

Tree-sitter Query

142

5,821 commits

updated Sep 21, 2026

See the code

README

nvim-treesitter

[!WARNING] This project is deprecated. Please use nvim-treesitter/nvim-treesitter — see Migrating to upstream below.

Status

This fork was born from a gap, and is closing because the gap closed.

The gap. Upstream nvim-treesitter went dormant and its repository was archived after years in which nearly all maintenance had serialized on a very small team. This fork was an attempt to keep the project alive by removing that single point of failure: instead of one monolith owning every language's queries, responsibility would be distributed to the people closest to each language — grammar owners and heavy users — backed by per-language query repos (nvim-treesitter-queries-<lang>), a shared parser registry for versioning, and CI validating every query change against its parser.

Finding 1: the gap closed. Upstream was resurrected by its original maintainers and is actively developed again, carrying the same main-branch rewrite this fork was based on. When the canonical project is healthy, a parallel universe of ~330 per-language repos is more coordination than value.

Finding 2: distributed maintenance only partly landed. Some query repos found maintainers; most didn't. This turned out to be structural, not a recruiting problem: a grammar is written once, but its queries are perpetual, editor-specific upkeep, so grammar owners rarely want the second job even when the infrastructure is handed to them. Upstream independently reached the same conclusion a few months later, dropping its own query-maintainer roster with the note that "most maintainers stopped being responsive very quickly." Two systems, one lesson: distributing the structure of responsibility doesn't distribute the labor.

What lives on: the zsh grammar maintained by this fork's author was upstreamed and is now upstream's parser for zsh; the query-validation CI setup is worth borrowing; and the registry experiment remains preserved below and across the org for anyone who wants to pick it up.

Thank you to everyone who starred, filed issues, adopted a query repo, or contributed along the way. Please point your configs at upstream — the migration notes cover the one rough edge (old copied query directories need resetting).

Migrating to upstream

Upstream installs queries as symlinks into its own runtime; this fork copied them into ~/.local/share/nvim/site/queries/<lang>/ as real directories, which upstream's installer cannot replace — you will see EEXIST: file already exists errors from :TSUpdate until you reset them:

rm -rf "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/queries

Then reinstall with the bang (plain :TSInstall / :TSUpdate skips already-installed parsers without relinking queries):

:TSInstall! <your languages>

[!CAUTION] This is a full, incompatible rewrite. Treat it as a new plugin and set it up from scratch following the instructions below. If you need the previous version, use the master branch (locked, no new features).


Requirements

RequirementVersion
Neovim0.10.0 or later
treesitter-parser-registrylatest
tree-sitter CLI0.26.1 or later — install via your system package manager, not npm
C compilersee cc requirements
curlany recent version (used for HTTP downloads)

[!IMPORTANT] Neovim support tracks the latest stable release and the latest nightly prerelease only. Other versions may work but are not tested.


Installation

lazy.nvim

{
  'nvim-treesitter/nvim-treesitter',
  dependencies = { 'neovim-treesitter/treesitter-parser-registry' },
  lazy = false,
  build = ':TSUpdate',
}

[!IMPORTANT] This plugin does not support lazy-loading.

Other plugin managers

Add :TSUpdate as a post-install / post-update build step.


Quick start

You do not need to call setup unless you want to change the install directory.

-- optional — only needed to override the default install_dir
require('nvim-treesitter').setup {
  -- parsers and queries are installed here (prepended to runtimepath)
  install_dir = vim.fn.stdpath('data') .. '/site',
}

Install parsers and their queries:

require('nvim-treesitter').install { 'rust', 'python', 'typescript' }

Then enable features per language. Features are not enabled automatically.

vim.api.nvim_create_autocmd('FileType', {
  pattern = { 'rust', 'python', 'typescript' },
  callback = function()
    vim.treesitter.start()                                    -- highlighting
    vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'     -- folds
    vim.wo.foldmethod = 'expr'
    vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" -- indentation
  end,
})

Commands

CommandDescription
:TSInstall {lang...}Install parsers and queries. No-op if already installed.
:TSInstall! {lang...}Force reinstall (useful after upgrading the plugin).
:TSUpdate [{lang...}]Update installed parsers and queries to latest release. Omit languages to update all.
:TSUpdate! [{lang...}]Update, bypassing the per-parser version cache (24h TTL).
:TSUninstall {lang...}Remove parsers and queries for the specified languages.
:TSRegistryUpdateForce-refresh the registry from GitHub, bypassing the 7-day cache.
:TSStatusOpen a status buffer showing installed version vs. latest for each language.
:TSLogShow log from the last install/update/uninstall run.

Supported languages and features

Languages are discovered from the neovim-treesitter registry. Any language in the registry can be installed; there are no tiers.

Registry and version caching

Two caches avoid unnecessary network requests:

  1. Registry cache (7-day TTL) — the full registry.json is fetched from GitHub on first use and stored at <stdpath('data')>/site/registry/. Subsequent loads within 7 days use the cached copy. If a fetch fails, the stale cache is used as a fallback.

  2. Per-parser version cache (24-hour TTL) — for each installed language, the latest known parser and query versions are cached so :TSUpdate doesn't hit the network for every language on every run.

To refresh each cache independently:

" Force re-fetch the registry (e.g. after a new language is added)
:TSRegistryUpdate

" Force re-check per-parser versions (bypasses the 24h cache)
:TSUpdate!

What is installed

For each language, nvim-treesitter installs:

  • Parser — a compiled .so fetched from the upstream grammar repository
  • Queries.scm files sourced from whichever location the registry designates:
    • A community query repo (nvim-treesitter-queries-<lang> under the neovim-treesitter GitHub org) for external_queries languages
    • The parser repo itself for self_contained languages (where the parser author ships Neovim queries alongside the grammar)

The source type is transparent to users — :TSInstall handles both.

Supported query types

Query fileFeatureHow to enable
highlights.scmSyntax highlightingvim.treesitter.start()
injections.scmMulti-language documentsautomatic after start()
folds.scmTreesitter-based foldsvim.wo.foldmethod = 'expr'
indents.scmTreesitter-based indentationvim.bo.indentexpr = ...
locals.scmScope/definition lookupused by other plugins

Local overrides

Override queries for a language

Query files in your Neovim config's runtimepath take precedence over installed queries. Place a file at:

~/.config/nvim/queries/<lang>/<type>.scm

To extend (not replace) the installed queries, add this as the first line:

; extends

To replace them entirely, omit that line.

See :h treesitter-query-modelines for details.

Use a local parser checkout

Point install_dir at a directory you manage, then place your compiled parser and queries there directly:

<install_dir>/
  parser/
    <lang>.so          ← compiled parser binary
  queries/
    <lang>/
      highlights.scm   ← query files
require('nvim-treesitter').setup {
  install_dir = '/path/to/my/parsers',
}

Neovim will use parsers and queries from install_dir as long as it is on runtimepath, which setup ensures. You can still use :TSInstall for other languages alongside your local overrides.


Local parsers

To install a parser that is not in the registry, or to use your own fork of a parser, add it to local_parsers in setup(). Each value is a registry entry — the same shape used in registry.json — with a source field:

-- Local directory checkout (no network fetch)
require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'local',
        path         = '~/Development/tree-sitter-zsh',
        queries_path = 'nvim-queries/zsh',  -- subdir containing .scm files
      },
      filetypes = { 'zsh' },
    },
  },
}

-- Remote URL not in the registry (self_contained = ships its own queries)
require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'self_contained',
        url          = 'https://github.com/georgeharker/tree-sitter-zsh',
        semver       = false,
        queries_path = 'nvim-queries/zsh',
      },
      filetypes = { 'zsh' },
    },
  },
}

Then: :TSInstall zsh

If Neovim does not detect your language's filetype by default, register the parser name manually:

vim.treesitter.language.register('mylang', { 'ml' })

Contributing

Fixing or improving queries for an existing language

Each language's queries live in their own repository under the neovim-treesitter GitHub org:

https://github.com/neovim-treesitter/nvim-treesitter-queries-<lang>

Open a pull request there. CI will validate your changes automatically. See the contributing guide for the full workflow.

Adding a new language

  1. Open a pull request adding an entry to registry.json in the registry repo
  2. Create the nvim-treesitter-queries-<lang> repository following the query repo setup guide

Alternatively, if you maintain the parser itself you can ship queries directly from your parser repo using the self-contained model — see the self-contained migration guide.

Claiming maintainership of a language repo

Add yourself to the CODEOWNERS file in the language's query repo and open a PR. See the governance guide for details.


Migrating from the previous version

The previous version of nvim-treesitter pinned specific parser revisions inside the plugin and used a tiered system (stable/unstable/unmaintained). The new version:

  • Discovers languages and their latest versions from the community registry
  • Fetches queries from per-language repos maintained by language communities
  • Has no tiers — if a language is in the registry it can be installed
  • No longer uses parsers.lua — per-language config via parsers.<lang> = {} is not supported in this version
  • Removes :TSInstallFromGrammar — parser generation from grammar source is not part of the installer's scope

Plugin manager build step

Replace any reference to tiers in your build step:

-- before (no longer valid)
build = ':TSUpdate stable'

-- after
build = ':TSUpdate'

Custom parser entries

The old pattern using a User TSUpdate autocmd no longer works:

-- OLD — no longer supported
vim.api.nvim_create_autocmd('User', {
  pattern = 'TSUpdate',
  callback = function()
    require('nvim-treesitter.parsers').zsh = {
      install_info = {
        url = 'https://github.com/georgeharker/tree-sitter-zsh',
        queries = 'nvim-queries/zsh',
      },
      tier = 1,
    }
  end,
})

Use local_parsers in setup() instead:

-- NEW — remote URL (self_contained: ships its own queries)
require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'self_contained',
        url          = 'https://github.com/georgeharker/tree-sitter-zsh',
        queries_path = 'nvim-queries/zsh',
      },
      filetypes = { 'zsh' },
    },
  },
}

For a local directory checkout instead of a remote URL:

require('nvim-treesitter').setup {
  local_parsers = {
    zsh = {
      source = {
        type         = 'local',
        path         = '~/Development/tree-sitter-zsh',
        queries_path = 'nvim-queries/zsh',
      },
      filetypes = { 'zsh' },
    },
  },
}

Then install as normal: :TSInstall zsh.

Old fieldNew fieldNotes
url = '...'source.url = '...'moved under source
path = '...'source.path = '...'moved under source, implies type = 'local'
location = '...'source.location = '...'monorepo subdir, moved under source
queries = 'subdir'source.queries_path = 'subdir'renamed and moved under source
semver = falsesource.semver = falsemoved under source
revision / min_version(removed)version managed by the registry
tier(removed)no tiers in new system
generate / generate_from_json(removed)parser generation not supported

Health check

Run :checkhealth nvim-treesitter to verify the installation.

Contributors

(top 30 of 373)

web-flow

878 commits

theHamsta

680 commits

clason

494 commits

Languages

Tree-sitter Query

67.1%

Lua

20.9%

Cap'n Proto

2.8%

Shell

2.1%