Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tree-sitter/tree-sitter/llms.txt

Use this file to discover all available pages before exploring further.

The Query API provides functions to create, execute, and manage queries programmatically. This reference covers the C API, which is the foundation for language bindings.

Creating Queries

ts_query_new

Create a query by specifying a string containing one or more patterns.
TSQuery *ts_query_new(
  const TSLanguage *language,
  const char *source,
  uint32_t source_len,
  uint32_t *error_offset,
  TSQueryError *error_type
);

Parameters

language
const TSLanguage*
required
The language to create the query for. Must match the grammar used to parse the target syntax trees.
source
const char*
required
A string containing one or more query patterns written in the query syntax.
source_len
uint32_t
required
The length of the source string in bytes.
error_offset
uint32_t*
Output parameter. If there’s an error, this will be set to the byte offset where the error occurred.
error_type
TSQueryError*
Output parameter. If there’s an error, this will be set to the error type.

Returns

A pointer to a TSQuery object, or NULL if there was an error.

Error Handling

If the query has syntax errors or references invalid node types or fields, the function still returns a TSQuery, but error_offset and error_type indicate the problem.
The TSQuery value is immutable and can be safely shared between threads.

Error Types

TSQueryError

Enumerates possible query errors:
typedef enum {
  TSQueryErrorNone = 0,
  TSQueryErrorSyntax,
  TSQueryErrorNodeType,
  TSQueryErrorField,
  TSQueryErrorCapture,
} TSQueryError;
TSQueryErrorNone
0
No error occurred. The query is valid.
TSQueryErrorSyntax
The query has a syntax error. Check the S-expression structure.
TSQueryErrorNodeType
The query references a node type that doesn’t exist in the grammar.
TSQueryErrorField
The query references a field name that doesn’t exist in the grammar.
TSQueryErrorCapture
The query has an invalid capture name or usage.

Creating Query Cursors

ts_query_cursor_new

Create a cursor for executing queries.
TSQueryCursor *ts_query_cursor_new(void);

Returns

A pointer to a new TSQueryCursor object.
Query cursors carry mutable state and should not be shared between threads. However, they can be reused for multiple query executions.

Executing Queries

ts_query_cursor_exec

Execute a query on a syntax node.
void ts_query_cursor_exec(
  TSQueryCursor *cursor,
  const TSQuery *query,
  TSNode node
);

Parameters

cursor
TSQueryCursor*
required
The query cursor to use for execution.
query
const TSQuery*
required
The query to execute.
node
TSNode
required
The syntax tree node to search within. The query will match against this node and all its descendants.

Iterating Over Matches

Data Structures

The API provides structures for representing captures and matches:
typedef struct {
  TSNode node;
  uint32_t index;
} TSQueryCapture;

typedef struct {
  uint32_t id;
  uint16_t pattern_index;
  uint16_t capture_count;
  const TSQueryCapture *captures;
} TSQueryMatch;
TSQueryCapture
Represents a single captured node.
  • node: The captured syntax tree node
  • index: The index of the capture in the query (corresponds to the order of @capture names)
TSQueryMatch
Represents a complete pattern match.
  • id: Unique identifier for this match
  • pattern_index: Which pattern in the query matched (0-based)
  • capture_count: Number of captures in this match
  • captures: Array of captured nodes

ts_query_cursor_next_match

Retrieve the next match from the query cursor.
bool ts_query_cursor_next_match(
  TSQueryCursor *cursor,
  TSQueryMatch *match
);

Parameters

cursor
TSQueryCursor*
required
The query cursor to retrieve matches from.
match
TSQueryMatch*
required
Output parameter. Will be populated with the next match’s data.

Returns

  • true if a match was found and match was populated
  • false if there are no more matches

Example Usage

Basic Query Execution

// Create a query
const char *query_source = "(function_declaration name: (identifier) @func-name)";
uint32_t error_offset;
TSQueryError error_type;

TSQuery *query = ts_query_new(
  language,
  query_source,
  strlen(query_source),
  &error_offset,
  &error_type
);

if (error_type != TSQueryErrorNone) {
  fprintf(stderr, "Query error at offset %u: %d\n", error_offset, error_type);
  return;
}

// Create a cursor
TSQueryCursor *cursor = ts_query_cursor_new();

// Execute the query on a syntax tree node
ts_query_cursor_exec(cursor, query, root_node);

// Iterate over matches
TSQueryMatch match;
while (ts_query_cursor_next_match(cursor, &match)) {
  printf("Match found with %u captures\n", match.capture_count);
  
  for (uint32_t i = 0; i < match.capture_count; i++) {
    TSQueryCapture capture = match.captures[i];
    // Process captured node
    uint32_t start = ts_node_start_byte(capture.node);
    uint32_t end = ts_node_end_byte(capture.node);
    printf("  Capture %u at bytes %u-%u\n", capture.index, start, end);
  }
}

// Clean up
ts_query_cursor_delete(cursor);
ts_query_delete(query);

Error Handling

TSQuery *create_query_safe(
  const TSLanguage *language,
  const char *source
) {
  uint32_t error_offset;
  TSQueryError error_type;
  uint32_t source_len = strlen(source);
  
  TSQuery *query = ts_query_new(
    language,
    source,
    source_len,
    &error_offset,
    &error_type
  );
  
  if (error_type != TSQueryErrorNone) {
    const char *error_name;
    switch (error_type) {
      case TSQueryErrorSyntax:
        error_name = "Syntax error";
        break;
      case TSQueryErrorNodeType:
        error_name = "Invalid node type";
        break;
      case TSQueryErrorField:
        error_name = "Invalid field";
        break;
      case TSQueryErrorCapture:
        error_name = "Invalid capture";
        break;
      default:
        error_name = "Unknown error";
    }
    
    fprintf(stderr, "%s at offset %u\n", error_name, error_offset);
    
    // Print context around error
    uint32_t start = error_offset > 20 ? error_offset - 20 : 0;
    uint32_t end = error_offset + 20 < source_len ? error_offset + 20 : source_len;
    fprintf(stderr, "Context: %.*s\n", end - start, &source[start]);
    
    if (query) {
      ts_query_delete(query);
    }
    return NULL;
  }
  
  return query;
}

Reusing Query Cursors

// Query cursors can be reused for better performance
TSQueryCursor *cursor = ts_query_cursor_new();

// Execute on first tree
ts_query_cursor_exec(cursor, query, tree1_root);
TSQueryMatch match;
while (ts_query_cursor_next_match(cursor, &match)) {
  // Process matches from tree1
}

// Reuse for second tree
ts_query_cursor_exec(cursor, query, tree2_root);
while (ts_query_cursor_next_match(cursor, &match)) {
  // Process matches from tree2
}

ts_query_cursor_delete(cursor);

Memory Management

  • Create with ts_query_new()
  • Use across multiple threads (read-only)
  • Delete with ts_query_delete() when done
  • Deletion is thread-safe if no queries are executing
  • Create with ts_query_cursor_new()
  • Use for one or more query executions (single thread)
  • Delete with ts_query_cursor_delete() when done
  • Do not share cursors between threads
  • TSQueryMatch is populated by reference
  • The captures array is valid until the next call to ts_query_cursor_next_match()
  • Copy data if you need to retain it longer

Advanced Features

Query Introspection

Queries expose information about their structure:
// Get the number of patterns in a query
uint32_t pattern_count = ts_query_pattern_count(query);

// Get the number of captures in a query
uint32_t capture_count = ts_query_capture_count(query);

// Get a capture name by index
const char *capture_name = ts_query_capture_name_for_id(
  query,
  capture_index,
  &name_length
);

Query Predicates

Access predicates and directives programmatically:
// Get predicates for a pattern
uint32_t predicate_step_count;
const TSQueryPredicateStep *predicate_steps = 
  ts_query_predicates_for_pattern(
    query,
    pattern_index,
    &predicate_step_count
  );

// Process predicate steps to implement filtering
// (Implementation depends on your language binding)
Predicate evaluation is typically implemented in language bindings, not the C library. The C API exposes predicates in structured form for bindings to interpret.

Language Bindings

Most users interact with queries through language bindings:

Rust

tree-sitter crate provides idiomatic Rust API with predicate support

JavaScript

web-tree-sitter and Node bindings offer promise-based APIs

Python

tree-sitter package provides Pythonic interface with predicate evaluation
See language-specific documentation for:
  • Predicate implementation
  • Iterator patterns
  • Integration with language features

Performance Considerations

1

Share Queries

Create queries once and share them across threads. Query creation has overhead, especially for complex patterns.
2

Reuse Cursors

Within a thread, reuse query cursors for multiple executions. Cursor creation and deletion have some overhead.
3

Limit Query Scope

Execute queries on the smallest node that contains your target. Don’t query the entire tree if you only need a function body.
4

Optimize Patterns

More specific patterns (with field names and node types) are faster than generic ones. Use wildcards sparingly.

Next Steps

Query Syntax

Learn how to write query patterns

Predicates

Understand how to implement and use predicates

Parsers

Learn about parsing and syntax trees

Language Bindings

Explore Tree-sitter APIs in other languages