tree-sitter for Node.js: a parser you bind to one grammar at a time
Node.js bindings for tree-sitter
At a glance
- What is it?
- The tree-sitter npm package wraps the tree-sitter parsing library for Node.js. It is small, it parses from a string or from a callback, and it exposes tree.edit() so an editor can reparse incrementally, but the version published to npm and the version tagged on GitHub have drifted apart.
- Who is it for?
- node-tree-sitter is a good fit for a Node.js tool that needs a real syntax tree rather than regular expressions, especially an editor or linter that has to survive incremental edits, and the grammar packages make adding a second language a matter of one more require. It is a poor fit if you need the newest upstream parser behaviour and cannot wait for the npm build to catch up, since the manifest sits at 0.25.1 while the most recent GitHub release is v0.22.4.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 86 days ago.
- What is it written in?
- Mainly JavaScript, 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
Installing the bindings and a grammar is two npm commands
The package installs from npm under the name tree-sitter.
npm install tree-sitterThat gets you the binding itself, not a parser for any particular language. Grammars are separate packages, and the documentation points at the tree-sitter-javascript package as an example of an existing grammar.
npm install tree-sitter-javascriptThe rule is that a grammar has to exist and be published before you can use it, and the documentation notes that new grammars can be developed with the tree-sitter CLI. That constraint shapes the whole design: the binding is generic, and everything language-specific lives in a package you choose. The manifest lists tree-sitter-javascript, tree-sitter-python, tree-sitter-rust, tree-sitter-json, tree-sitter-html, tree-sitter-java, tree-sitter-c and tree-sitter-embedded-template as development dependencies, which is the shape of the grammar ecosystem this package expects to sit on top of.
A parser holds exactly one grammar at a time
The usage pattern is short. You require the binding and the grammar, construct a parser, and hand it the language.
const Parser = require('tree-sitter');
const JavaScript = require('tree-sitter-javascript');
const parser = new Parser();
parser.setLanguage(JavaScript);Once the language is set, the parser turns source into a tree.
const sourceCode = 'let x = 1; console.log(x);';
const tree = parser.parse(sourceCode);The tree is navigated by node type. Printing the root gives the s-expression style structure, and a child is reached by index and then by firstChild, with each node carrying its type, its start and end positions and its start and end indices.
const callExpression = tree.rootNode.child(1).firstChild;There is no option object in the examples, no configuration of the lexer, and no way to attach several grammars to one parser in what is documented here. If you need to parse two languages side by side, the documented answer is two parsers. The documentation also points out that the full API documentation maps closely to the C API, though not exactly, so C examples need a second look before they translate directly.
tree.edit() is how a reparse becomes incremental
Reparsing from scratch on every keystroke is the obvious way to make a tree-sitter integration slow, and the binding gives you the mechanism to avoid it. When the source changes, you describe the edit on the old tree and pass that tree into the next parse.
const newSourceCode = 'const x = 1; console.log(x);';
// ^ ^
// indices: 3 5
// points: (0,3) (0,5)
tree.edit({
startIndex: 0,
oldEndIndex: 3,
newEndIndex: 5,
startPosition: {row: 0, column: 0},
oldEndPosition: {row: 0, column: 3},
newEndPosition: {row: 0, column: 5},
});The documentation notes that the end index is exclusive, and the example makes the arithmetic visible: the word let was three characters long and const is five, so the old end index is 3 and the new one is 5. Every field is spelled out, including the start and end positions for both the old and new text, which is more bookkeeping than most editors want to write by hand. Passing the edited tree to parser.parse is what lets the parser reuse the parts of the tree that did not change, and the documentation describes that reuse as being much faster than the first parse.
Parsing from a callback covers text that is not one string
Not every editor holds a file as a single string. If the source lives in a rope, an array of lines, or some other structure, the parser can be handed a callback instead of a string and asked for the text at a given position.
const sourceLines = [
'let x = 1;',
'console.log(x);'
];
const tree = parser.parse((index, position) => {
let line = sourceLines[position.row];
if (line) {
return line.slice(position.column);
}
});The callback receives an index and a position, and the example resolves the position to a row in the array and then slices at the column. The subtlety is what the callback returns. It hands back the remainder of the line from that column onward, not a single character, and the parser is what repeatedly asks for more. An implementation that returned one character per call would behave differently, and the example is the only specification of that contract in the documentation.
This is the feature that decides whether the binding fits your editor. A tool that already holds its document as one string can ignore it entirely, while a tool with a rope or a piece table can avoid flattening the buffer on every parse.
The native addon ships prebuilt binaries and a build hook
This is not a pure JavaScript package. The repository root contains binding.gyp, a src directory and a vendor directory that holds tree-sitter itself, and the manifest lists node-gyp-build and node-addon-api as runtime dependencies, with node-gyp and prebuildify among the development dependencies. The manifest also has an install script that runs node-gyp-build, and the published files include a prebuilds directory alongside the vendored tree-sitter sources.
That combination is the standard pattern for a native Node addon that wants to avoid compiling on the user's machine when a prebuilt binary exists. It also means the package carries a copy of the upstream C library, so the JavaScript version and the vendored parser version can drift apart. The manifest also declares documentation tooling, with typedoc and separate TypeScript configuration files, and a types entry pointing at tree-sitter.d.ts, so TypeScript users get declarations from the same package.
npm says 0.25.1, the release tags stop at v0.22.4
The versioning story is the part to check before you pin a dependency. The manifest in the repository declares version 0.25.1 for the package published to npm. The listed GitHub releases are older: v0.22.4 published on 2024-12-30, v0.21.1 on 2024-03-28 and v0.21.0 on 2024-03-17.
The last push to the repository was on 2026-07-29, so work is landing on the main branch and being published to npm, while the release tags in the repository stopped more than a year and a half earlier. If you track tags you get an old generation of the binding; if you track the npm version you get the current one, and the npm version is the one that matters for a JavaScript project.
The licence is MIT, which covers both the binding code and the vendored tree-sitter sources it ships. The grammar packages you install alongside are separate packages with their own licensing, and the documentation does not discuss that, so anyone redistributing a bundled tool needs to check the terms of each grammar it includes.
Editorial conclusion
node-tree-sitter is a good fit for a Node.js tool that needs a real syntax tree rather than regular expressions, especially an editor or linter that has to survive incremental edits, and the grammar packages make adding a second language a matter of one more require. It is a poor fit if you need the newest upstream parser behaviour and cannot wait for the npm build to catch up, since the manifest sits at 0.25.1 while the most recent GitHub release is v0.22.4. Before adopting it, check the version of the tree-sitter npm package your project would lock, and confirm the grammar packages you need are published for that same generation.
Frequently asked questions
How do I install tree-sitter in a Node.js project?
Run npm install tree-sitter for the binding, then install the grammar package for the language you want, for example npm install tree-sitter-javascript. The binding cannot parse anything until a grammar is loaded into it.
How does node-tree-sitter support incremental reparsing?
You describe the edit on the existing tree with tree.edit(), including the old and new start and end indices and positions, and then pass that tree into parser.parse along with the new source. The documentation notes that the end index is exclusive and that reusing the tree is much faster than the first parse.
Can node-tree-sitter parse text that is not a single string?
Yes. Instead of a string you pass a callback to parse that receives an index and a position and returns the text at that point, which is how a rope or an array of lines can be parsed without flattening it first.
Does the tree-sitter npm package require a native build?
The package is a native addon: the repository includes binding.gyp, a src directory and a vendored copy of tree-sitter, the manifest has an install script running node-gyp-build, and the published files include a prebuilds directory.
What is the latest version of the tree-sitter npm package?
The manifest in the repository declares version 0.25.1, while the most recent GitHub release listed for the repository is v0.22.4 from 2024-12-30. The last push to the repository was on 2026-07-29.
Official sources
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.
[](https://hysenlabs.com/projects/tree-sitter-node-tree-sitter)Was this useful?