> ## 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.

# Secondary Indexes

> For the complete documentation index optimized for AI agents, see [llms.txt](/llms.txt).

Secondary indexes accelerate property-based filtering on the labeled property graph. Two flavors
are available, each on both nodes and edges:

* **Equality indexes** map `property = value` to the set of matching IDs. Use them for exact-match
  lookups like "all users with `status = active`."
* **Range indexes** provide ordered scans over numeric and string properties. Use them for
  comparisons like `timestamp > T` or `score >= 4`.

Secondary indexes are opt-in. They speed up reads at the cost of additional write overhead and
storage. Only index properties that are frequently filtered on. Unindexed properties remain
queryable via `.where_(Predicate::...)`, but the route will scan the label rather than seek the
index.

Indexes address top-level properties only. Nested fields (e.g. `metadata.externalID`) can be
filtered with a dotted path but are scan-only in V1 — store frequently indexed metadata as
top-level properties.

The DSL fragments below are bare traversals. To execute them, wrap each one in a `read_batch()`
or `write_batch()` route as shown in [Querying](/database/querying).

## Equality Index — Nodes

Declare the index as part of your schema setup write batch:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().create_index_if_not_exists(
    IndexSpec::node_equality("User", "status"),
)
```

Insert a node carrying the indexed property:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().add_n(
    "User",
    vec![
        ("userId", PropertyInput::from("u-42")),
        ("status", PropertyInput::from("active")),
    ],
)
```

Query through the index using the source-predicate form. `n_with_label_where` pushes the
predicate down to the index so the route never scans the full label:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().n_with_label_where(
    "User",
    SourcePredicate::eq("status", "active"),
)
```

## Equality Index — Edges

Declare an equality index on an edge property:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().create_index_if_not_exists(
    IndexSpec::edge_equality("FOLLOWS", "since_year"),
)
```

Create an edge between two known nodes. `add_e` is called from a node-state traversal — the
current node is the edge's source, and the second argument is the target:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().n(NodeRef::param("source"))
    .add_e(
        "FOLLOWS",
        NodeRef::param("target"),
        vec![("since_year", PropertyInput::from(2024i64))],
    )
```

Query the edge index directly with `e_with_label_where`:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().e_with_label_where(
    "FOLLOWS",
    SourcePredicate::eq("since_year", 2024i64),
)
```

## Range Index — Nodes

Declare a range index on a numeric or string property:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().create_index_if_not_exists(
    IndexSpec::node_range("Event", "timestamp"),
)
```

`node_range` uses ascending physical order. Use `node_range_desc` or
`node_range_with_direction(..., RangeIndexDirection::Desc)` when the primary access
pattern is descending order, such as newest-first feeds:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().create_index_if_not_exists(
    IndexSpec::node_range_desc("Event", "timestamp"),
)
```

Range indexes use the same insert path as equality indexes; the property values are simply
ordered by the index. Query with any of `gt`, `gte`, `lt`, `lte`, or `between`:

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().n_with_label_where(
    "Event",
    SourcePredicate::gt("timestamp", 1_700_000_000i64),
)
```

## Range Index — Edges

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().create_index_if_not_exists(
    IndexSpec::edge_range("RATED", "score"),
)
```

For descending physical order, use `IndexSpec::edge_range_desc("RATED", "score")` or
`IndexSpec::edge_range_with_direction("RATED", "score", RangeIndexDirection::Desc)`.

```rust theme={"languages":{"custom":["languages/helixql.json"]}}
g().e_with_label_where(
    "RATED",
    SourcePredicate::gte("score", 4i64),
)
```

## See Also

For tenant-scoped equality lookups using the same secondary-index machinery, see
[Multi-Tenancy](/database/multi-tenancy).
