Documentation Index
Fetch the complete documentation index at: https://docs.helix-db.com/llms.txt
Use this file to discover all available pages before exploring further.
For the complete documentation index optimized for AI agents, see llms.txt.The DSL exposes two kinds of search source steps that return scored hits instead of a plain label scan: vector search (approximate k-NN over a float-array property) and text search (BM25 over a string property). Both are available on nodes and edges, both expose a literal form and a parameter-bound
_with form, and both can be the
first step of a longer traversal — once you have hits, you can keep walking the graph.
This page assumes the indexes exist. Index creation is covered first, then queries.
Creating indexes
Indexes are graph mutations, so they live in awriteBatch. The unified
createIndexIfNotExists(IndexSpec.*) step covers every variant and is idempotent —
safe to run on each deploy.
IndexSpec covers every shape the runtime supports:
| Spec | What it is |
|---|---|
nodeEquality(label, prop) | Secondary equality index. |
nodeUniqueEquality(label, prop) | Equality index with a uniqueness constraint. |
nodeRange(label, prop) | Range / ordering index. |
nodeVector(label, prop, tenant?) | ANN vector index. |
nodeText(label, prop, tenant?) | BM25 text index. |
edgeEquality / edgeRange / edgeVector / edgeText | Same shapes for edges. |
tenant? is the multi-tenancy partition key — see the bottom of this page.
Vector search
vectorSearchNodes(label, property, queryVector, k, tenantValue?) returns the top
k nodes whose property is closest to queryVector. Hits arrive in ascending
distance order with a virtual $distance field projected onto each result.
$distance is only present on the direct hit stream. The instant you step off it
with .out(), .in(), .both(), .outN(), etc., the score is gone — project it
before you traverse.
Following the graph from a hit
Vector hits are a normal node stream, so the next step can keep walking. The example below finds posts similar to a query vector, then returns their authors with the post’s distance projected as an annotation.varAs blocks duplicate the search step because each is a fresh traversal
binding. If you only want the authors and don’t need the hits themselves in the
response, drop the first varAs and remove "hits" from returning(...).
Text search (BM25)
textSearchNodes(label, property, queryText, k, tenantValue?) runs BM25 over the
named text-indexed property and returns the top k matching nodes. The interface
mirrors vectorSearchNodes exactly, including the $distance virtual field (here it
represents an inverted relevance score — smaller is more relevant, just like vector
distance).
Parameter-bound search
For routes that take the query vector / text andk from the request, switch to the
_with siblings. They accept PropertyInput.param(...) for the query and a
StreamBound (literal or Expr-wrapped) for k.
textSearchNodesWith(...) has the identical shape — just replace
PropertyInput.param("query_vector") with PropertyInput.param("query_text") and
declare the parameter as param.string().
Multi-tenancy
If the index was created with atenantProperty, every search must supply a matching
tenantValue (the partition key). Pass it as the fifth argument to
vectorSearchNodes / textSearchNodes, or wrap it in PropertyInput.param("...")
inside the _with variants.
Next Steps
Advanced patterns
repeat, union, choose, coalesce, optional, forEachParam.Parameters and bundles
Declare parameters once, then expose them via dynamic requests or stored bundles.