Guide
HelixDB accepts Cypher statements alongside its native operation-tree queries.
Both languages run against the same graph, transactions, and indexes, so data
written with one is visible to the other, and existing databases need no
migration.
Cypher support is a profile of openCypher, not full compatibility. The clauses,
expressions, and functions listed under What is supported run.
Everything in What is not supported is rejected with a
specific error before the statement reads or writes anything.
Cypher as described here is served by local servers running the
ghcr.io/helixdb/helixdb:v0.0.13 image or later, and by Helix Cloud gateways.
The SDK examples below require SDKs built from the
repository checkout. TypeScript 3.2.0,
Python 0.3.4, Rust 3.0.0, and Go v0.3.1 do not include the Cypher methods.
With those releases, use the HTTP API to run and explain statements.
Run a statement
Send one statement, with optional parameters and a query name, toPOST /v2/cypher. The server works out whether the statement reads or writes.
columns and rectangular rows, with one
value per column in column order:
query is required; parameters and query_name are optional. Duplicate rows
are kept unless the statement uses DISTINCT, and row order is only defined by
ORDER BY. A write without RETURN succeeds with empty columns and rows.
Parameters hold values. Labels, relationship types, and variable names are part
of the statement, so keep values in parameters instead of building them into the
query text.
Over HTTP
A local server has no authentication:POST /v2/query,
with the cluster API key and, on a GA shared gateway, the database ID:
403 for a statement that writes. The
HTTP API reference describes
the headers, status codes, and lossless value encoding in full.
From the CLI
From a linked local project,helix cypher runs a statement against the local
instance:
Request options
The native request options apply to Cypher too:
Rust and TypeScript set options on
request_builder() / requestBuilder() before
.cypher(...); Go and Python pass them to Cypher and cypher. A warm-only read
that Helix Cloud answers with 204 No Content returns no rows. Embedded clients
reject request options.
Explain a statement
POST /v2/cypher/explain takes the same body and plans the statement without
running it, so explaining a write changes nothing. The plan reports the
statement’s effect (Read or Write), its bindings, the selected access paths
such as index lookups, estimated cardinalities, blocking work, Cartesian
products, and optimizer warnings. Planning checks parameters, so supply every
parameter the statement uses.
What is supported
Create and read a small graph:
WHERE of a WITH filters the rows that clause returns, after its
DISTINCT, ORDER BY, SKIP, and LIMIT: WITH x ORDER BY x LIMIT 3 WHERE x > 2
keeps at most the third row. After SKIP or LIMIT, only the variables the
WITH projects remain in scope.
What is not supported
These constructs are valid openCypher but outside the profile. Each is rejected before anything runs, with the error categoryunsupported_feature and the
details.detail code shown, unless the table says otherwise.
Some of these fail as ordinary compile errors instead:
CREATE INDEX ...is asyntax_error(unexpected_syntax). Create indexes with the native secondary, vector, and text index APIs; Cypher planning then uses compatible indexes automatically.- A relationship in
CREATEneeds exactly one type and a direction, otherwise the statement is asyntax_error(no_single_relationship_type). - A parameter map in a
MATCHpattern, such asMATCH (n:User $props), is asyntax_error(invalid_parameter_use). Write the properties as a map literal, such as{email: $email}. - An unrecognized function name is a
syntax_error(unknown_function). - A statement that is also invalid openCypher reports the standard error, such as
variable_already_boundfor aCREATEthat redeclares a variable.
Values and parameters
Nulls, booleans, strings, lists, maps, and finite floats use ordinary JSON values, in parameters and in results. Integers inside JavaScript’s safe range use JSON numbers. Larger integers and non-finite floats use tagged values:
Node and relationship IDs are unsigned decimal strings in separate namespaces;
keep them as strings.
id() reports an overflow when an ID does not fit a signed
64-bit integer. Relationship endpoints keep their stored direction even when a
path traverses them in reverse. Internal metadata is left out of property maps.
Scalars, lists, and maps, including their tagged forms, can be passed as
parameters; nodes, relationships, and paths cannot.
The SDKs keep tagged values in results. TypeScript also accepts bigint
parameters and sends them tagged.
Writes and transactions
Each statement that writes runs in one transaction, including its index changes. If any clause fails, nothing the statement wrote is kept.- A created node needs exactly one non-empty label, and a created relationship
exactly one non-empty type.
MATCHwithout a label scans all nodes. - Stored properties are scalars or lists of one scalar type. Maps and nested lists can be used in expressions but not stored, so storing one fails the statement.
SET n += {...}updates the given keys and keeps the rest.SET n = {...}replaces every user property. Setting a property tonullremoves it.SETitems run in order, so later items see earlier changes.- A plain
DELETEof a node that still has relationships fails the statement.DETACH DELETEremoves the relationships and the node. - Index rules still apply: a value an index cannot store fails the statement, including a string too large for an index key (about 64 KiB).
- Do not retry a write automatically when its commit outcome is uncertain; see error handling.
Functions, aggregation, and grouping
toInteger()andtoInt()convert decimal strings exactly, including scientific notation, and truncate fractions toward zero:toInteger('9007199254740993.0')returns9007199254740993. Invalid text and values outside the signed 64-bit range return null.avg()ignores nulls, returns null for an empty group, and returns a float. A large total does not overflow while the inputs are finite.sum()reports integer overflow.- Expressions in
RETURNorWITHwithout aggregates define the groups. In an expression that mixes aggregation with other operations, everything outside the aggregate’s arguments must be a constant, a parameter, a grouped variable, or a projected property, following the openCypher grouping rules. An ambiguous aggregation is rejected before anything runs. ORDER BYcan use an aggregate without returning it, as inRETURN 1 + sum(amount) AS total ORDER BY 2 - sum(amount).- A simple
CASE value WHEN ...evaluatesvalueonce, andnullnever matchesnull; test for a missing value withCASE WHEN value IS NULL THEN .... Only the selected branch is evaluated, but aggregate arguments insideCASEare always evaluated.
Indexes and performance
Cypher plans use existing native indexes automatically. You do not name an index in the statement; explain shows which one was chosen.- An equality on a labeled node, such as
MATCH (u:User {email: $email})orWHERE u.email = $email, can read an equality index. So canINlists of up to 64 values andORed equalities on one property. - A range such as
WHERE u.age >= 21 AND u.age < $maxcan read a range index, and stops reading as soon as aLIMITis satisfied. ORDER BYon a range-indexed property in the index’s direction, with a range bound and aLIMIT, reads the index in order instead of sorting.id(n) = $idandid(n) IN $idsread those nodes directly.- An equality against an earlier variable, as in
UNWIND $ids AS k MATCH (u:User {uid: k}), looks up the index once per row. - An expansion only reads the relationship types it names, so
-[:WROTE|LIVES_IN]->skips a node’s other relationships. LIMITstops scans and expansions early when the clauses before it cannot fail. Reading a stored property inRETURNcan fail, so return whole nodes, or applyWITH ... LIMITfirst and read properties afterwards:MATCH (u:User) WITH u LIMIT 10 RETURN u.name.
AND, OR,
XOR, and NOT combinations over literals, parameters, variables, and properties
qualify. Arithmetic and function calls keep the label scan.
Limits
A statement that exceeds a budget fails with
resource_limit (HTTP 429)
instead of returning a truncated result, and a write that fails this way commits
nothing. UNWIND range(...) streams its values, while range() in an
expression builds the whole list first.
Errors
A failed statement returns the Cypher error body:error is the category and details.detail the specific condition, both in
lower snake case. phase is compile (nothing ran) or runtime, and span
gives the byte range of the statement text that failed, when known. Request,
authentication, storage, and transaction failures use the native error envelope.
The error reference
lists every category with its HTTP status and retry guidance.
Next steps
HTTP API
Request headers, status codes, and the OpenAPI contract for the Cypher routes.
Error handling
Cypher error categories, HTTP statuses, and when to retry.
Reading data
The native read API, with Cypher equivalents.
Secondary indexes
Create the indexes that Cypher plans read.