Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Output Values

⚠️ Warning

HelixQL is deprecated in HelixDB v2. Queries are now written with the Rust DSL and dispatched as JSON — see the Querying guide. This section is kept as a reference for legacy HelixQL projects.

For the complete documentation index optimized for AI agents, see llms.txt.

HelixQL queries end with a RETURN clause. You can return bindings (variables), projected properties, aggregations, literals, or choose to return nothing at all.

Quick reference

Return TypeSyntaxReturn TypeSyntax
Return a bindingRETURN usersExclude fieldsRETURN users::!{ email }
Return multipleRETURN user, postsAggregation/scalarRETURN count
Project fieldsRETURN users::{ name, age }LiteralRETURN "ok"
Only IDsRETURN users::IDNo payloadRETURN NONE

⚠️ Warning

When using the Python SDK, the output values are wrapped in an array for multiple query calls, so you will need to access the first element of the array to get the result of the first call.

Returning bindings

Return any previously bound value from your traversal.

QUERY GetAllUsers() =>
    users <- N<User>
    RETURN users
N::User {
    name: String,
    age: U8,
    email: String
}
{
  "users": [
    { "name": "Alice", "age": 25, "email": "alice@example.com" },
    { "name": "Bob", "age": 30, "email": "bob@example.com" },
    { "name": "Charlie", "age": 28, "email": "charlie@example.com" },
  ]
}

Returning multiple values creates multiple top-level fields in the response, named after the variables.

QUERY GetUserAndPosts(user_id: ID) =>
    user <- N<User>(user_id)
    posts <- user::Out<User_to_Post>
    RETURN user, posts
N::User {
    name: String,
    age: U8,
    email: String
}

N::Post {
    title: String,
    content: String
}

E::User_to_Post {
    From: User,
    To: Post
}
{
  "user": {"name": "Alice", "age": 25, "email": "alice@example.com"},
  "posts": [ 
    {"title": "My First Post", "content": "This is my first blog post!"}, 
    ...,
  ]
}

Returning projections and properties

Use property projection to shape the returned data.

QUERY FindUsers() =>
    users <- N<User>::RANGE(0, 10)
    RETURN users::{ name, age }
N::User {
    name: String,
    age: U8,
    email: String
}
{
  "users": [
    { "name": "Alice", "age": 25 },
    { "name": "Bob", "age": 30 },
    { "name": "Charlie", "age": 28 }
  ]
}

Return just the ID of each element:

QUERY FindUserIDs() =>
    users <- N<User>::RANGE(0, 10)
    RETURN users::ID
N::User {
    name: String,
    age: U8,
    email: String
}
{
  "users": [
    "c2ca233f-0cd8-4fae-8136-b40593792071",
    "d3db344g-1de9-5gbf-9247-c51604803082",
    "e4ec455h-2ef0-6hcg-0358-d62715914193"
  ]
}

Exclude specific properties:

QUERY FindUsersNoPII() =>
    users <- N<User>::RANGE(0, 10)
    RETURN users::!{ email, location }
N::User {
    name: String,
    age: U8,
    email: String,
    location: String
}
{
  "users": [
    { "name": "Alice", "age": 25 },
    { "name": "Bob", "age": 30 },
    { "name": "Charlie", "age": 28 }
  ]
}

You can also create nested or remapped shapes in RETURN using nested mappings:

QUERY FindFriends(user_id: ID) =>
    user <- N<User>(user_id)
    posts <- user::Out<User_to_Post>::RANGE(0, 20)
    RETURN user::|u|{
        userID: u::ID,
        posts: posts::{
            postID: ID,
            creatorID: u::ID,
            ..
        }
    }
N::User {
    name: String,
    age: U8,
    email: String
}

N::Post {
    title: String,
    content: String
}

E::User_to_Post {
    From: User,
    To: Post
}
{
  "user": {
    "userID": "c2ca233f-0cd8-4fae-8136-b40593792071",
    "posts": [
      {
        "postID": "d3db344g-1de9-5gbf-9247-c51604803082",
        "creatorID": "c2ca233f-0cd8-4fae-8136-b40593792071",
        "title": "My First Post",
        "content": "This is my first blog post!"
      },
      {
        "postID": "e4ec455h-2ef0-6hcg-0358-d62715914193",
        "creatorID": "c2ca233f-0cd8-4fae-8136-b40593792071",
        "title": "Weekend Plans",
        "content": "Planning to explore the city."
      }
    ]
  }
}

See property access, remappings, and exclusion for more details.


Returning scalars and literals

Aggregations and scalar bindings can be returned directly:

QUERY CountUsers() =>
    user_count <- N<User>::COUNT
    RETURN user_count
N::User {
    name: String,
    age: U8,
    email: String
}
{
  "user_count": 42
}

You can also return literals (strings, numbers, booleans) when useful:

QUERY DeleteCity(city_id: ID) =>
    DROP N<City>(city_id)
    RETURN "success"
N::City {
    name: String,
    population: U32
}
{
  "result": "success"
}

Returning nothing

For mutations or maintenance operations where you do not want a response payload, use RETURN NONE.

QUERY DeleteCityQuietly(city_id: ID) =>
    DROP N<City>(city_id)
    RETURN NONE
N::City {
    name: String,
    population: U32
}
{}

RETURN NONE signals that the query intentionally produces no output values. This is handy to avoid sending placeholder strings like “success” when a silent acknowledgement is preferred.