Skip to main content
For the complete documentation index optimized for AI agents, see llms.txt.
Send a dynamic query to POST /v1/query on a local or Helix Cloud instance. You can supply the query as a raw JSON request (--file/--json) or write it in the TypeScript DSL (-e/--ts/--ts-file) and let the CLI build the request for you.

Usage

helix query [INSTANCE] (--file <REQUEST.json> | --json '<JSON>' | -e '<TS>' | --ts-file <QUERY.ts>) [OPTIONS]
Provide exactly one input: a JSON request (--file or --json) or a TypeScript DSL expression (-e/--ts or --ts-file). The four input flags are mutually exclusive.

Arguments

ArgumentDescription
INSTANCEInstance name from helix.toml. Defaults to dev if omitted.

Available flags

Exactly one input flag is required (--file, --json, -e/--ts, or --ts-file).
FlagTypeRequiredDescriptionDefault
-f, --filePathOne input requiredJSON request body file.
--jsonStringOne input requiredInline JSON request body.
-e, --tsStringOne input requiredTypeScript DSL expression, evaluated locally to build the request (like mysql -e). See TypeScript DSL input.
--ts-filePathOne input requiredPath to a .ts file containing a TypeScript DSL expression.
--warmBooleanNoSend the request as a cache-warming call (X-Helix-Warm: true). Read requests only.false
--hostStringNoOverride the host for local instances.localhost
--portNumberNoOverride the port for local instances.[local.<instance>] port (default 6969)
--compactBooleanNoPrint compact JSON instead of pretty-printed JSON.false

Request body

The JSON request must include:
  • request_type — lowercase "read" or "write". Anything else is rejected with request_type must be lowercase 'read' or 'write'.
  • query_name — optional top-level query name for gateway logs and query diagnostics. Use exactly query_name; name and queryName are not accepted aliases. Missing or null falls back to __dynamic__.
  • query — the dynamic query object: a queries array of { "Query": { "name", "steps", "condition" } } entries plus a returns list naming what to return (see the example below). Missing query is rejected with dynamic query request must include query.
  • parameters — optional object of named parameters.
helix init local scaffolds examples/request.json with a runnable read request:
{
  "request_type": "read",
  "query_name": "node_count",
  "query": {
    "queries": [
      {
        "Query": {
          "name": "node_count",
          "steps": [
            { "NWhere": { "Eq": ["$label", { "String": "User" }] } },
            "Count"
          ],
          "condition": null
        }
      }
    ],
    "returns": ["node_count"]
  },
  "parameters": {}
}
Every query must begin with a source step (for example NWhere) before any terminal aggregator like Count. Use --file for checked-in or reusable requests, and --json for quick one-off requests from a shell. Quote inline JSON so your shell passes it as one argument.

TypeScript DSL input

Instead of hand-writing JSON, you can pass a TypeScript DSL expression and let the CLI build the request — the same way mysql -e runs SQL from the shell:
# Inline expression with -e / --ts
helix query dev -e 'readBatch().varAs("c", g().nWithLabel("User").count()).returning(["c"])'

# Or from a .ts file
helix query dev --ts-file queries/count_users.ts
How it works:
  • The expression must evaluate to a readBatch() or writeBatch() builder. g, readBatch, writeBatch, defineParams, and param are auto-imported and already in scope — you write only the expression, no imports.
  • The CLI evaluates it locally with Node using the published @helix-db/helix-db SDK, calls .toDynamicJson(), and posts the resulting dynamic-query JSON to /v1/query. The builders are pure (no I/O), so no instance needs to be running to build the request — only to run it. There is no separate compile step.
  • request_type is inferred automatically from whether you used readBatch() or writeBatch().
Requirements:
  • Node.js 20+ on PATH (npm ships with it). On first use the CLI installs the SDK once into its cache directory; later runs reuse it.
  • For inline -e use, write a single expression with no TypeScript type annotations (it is evaluated as an expression, not compiled). Put more elaborate queries in a --ts-file.
If Node is missing, the CLI tells you to install Node 20+ or fall back to --json/--file. For the full DSL surface, see the helix-query-typescript skill.

Validation rules

RuleError
request_type is missingdynamic query request must include request_type
request_type is not lowercase read or writerequest_type must be lowercase 'read' or 'write'
query_name is blank or whitespace-onlyquery_name must be non-empty when provided
name or queryName is used instead of query_nameInvalid request body / unknown field
query is missingdynamic query request must include query
--warm is used with a write request--warm is only valid for read requests
Neither --file nor --json is providedClap usage error
Both --file and --json are providedClap conflict error

Helix Cloud targets

For a Helix Cloud instance, helix query reads [enterprise.<instance>] in helix.toml and:
  • Posts to <gateway_url>/v1/query.
  • Sends an auth header named by query_auth_header (default Authorization) with the value read from the environment variable query_auth_env (default HELIX_API_KEY). The value is read from your shell environment or from a .env file in the project root (whichever is set), so you can keep the key out of your shell history.
If gateway_url is missing, the CLI returns:
Enterprise gateway URL is not configured for '<instance>'. Run 'helix sync <instance>' or set gateway_url in helix.toml.
If the auth env var is missing, the CLI returns Environment variable <NAME> is required for Enterprise query auth.

Output

  • A non-empty JSON response is pretty-printed by default; pass --compact to print on a single line.
  • 204 No Content (returned for --warm requests) produces no output and exits successfully.
  • Non-2xx responses produce Query failed with HTTP <status>: <body> and exit with a non-zero status.

Examples

# Send the example request to the local 'dev' instance
helix query dev --file examples/request.json

# Run the same request as a warm-up — populates per-process caches without printing output
helix query dev --file examples/request.json --warm

# Print compact JSON suitable for piping into jq
helix query dev --file examples/request.json --compact | jq '.node_count'

# Send an inline JSON request body
helix query dev --json '{"request_type":"read","query_name":"ad_hoc_read","query":{"queries":[],"returns":[]},"parameters":{}}'

# Build the request from a TypeScript DSL expression instead of JSON
helix query dev -e 'readBatch().varAs("c", g().nWithLabel("User").count()).returning(["c"])'

# Target a Helix Cloud instance (requires HELIX_API_KEY in env and gateway_url in helix.toml)
helix query production --file examples/request.json
  • helix start — start a local instance to query against
  • helix sync — refresh Helix Cloud gateway/auth metadata