Docs
Tutorials
How-to guides
Reference
Explanation
Release notes
Search
Docs
/

Object - Query

Type identifier: object:query Category: Object Operations

Description

Queries the Records of a Schema that match a filter, with pagination and ordering. Returns the matching Records with their metadata, or, with a select/groupBy shape, rows of chosen fields, aggregates over relationships, and grouped counts and totals. Use this node to retrieve data for processing, display, or downstream operations.

Input Handles

Handle

Data Type

Required

Description

<variableId>

Varies

Varies

Dynamic handles for filter variables

Input Handle Details

Variable Handles

Input handles are dynamically generated based on the variables configuration:

  • Handle ID: Variable's id property
  • Type: Defined by the variable's type configuration
  • Required: Based on variable's required property
  • Notes: Variables are used to populate filter expressions

Output Handles

Handle

Data Type

Description

results

object/array

Query results (single or array)

Output Handle Details

results

  • Type: Object (if firstOnly) or Array (if multiple)
  • With select or groupBy: one row per Record (or per group), keyed by the select entries' output names, typed from the shape. See Selecting fields, aggregates and groups.
  • Includes (without select or groupBy):

    • All Record properties
    • id: Record ID
    • schemaId: Schema ID
    • createdAt: Creation timestamp
    • updatedAt: Last update timestamp
    • createdById: Creator ID
    • updatedById: Last updater ID
  • Notes:

    • Returns null if firstOnly and no results
    • File entries are augmented with Record context

Configuration Options

Property

Type

Required

Default

Description

label

string

Yes

"Object - Query"

Display label for the node

schemaId

string

Yes

""

CUID2 of the Schema to query

variables

array

No

undefined

Variable definitions for filtering

filter

object

No

{}

Filter criteria

firstOnly

boolean

No

undefined

Return single Record vs array

limit

number

No

25

Maximum Records to return (capped at 1000)

offset

number

No

0

Skip N Records

order

"asc" / "desc"

No

"desc"

Sort order

orderBy

string

No

"createdAt"

Property to sort by

select

EntityQuerySelectEntry[]

No

undefined

The output shape: fields, aggregates and group keys. See Selecting fields, aggregates and groups

groupBy

EntityQueryGroupByEntry[]

No

undefined

Groups the Records: one result row per group

Configuration Details

variables

  • Type: Array of variable definitions
  • Notes:

    • Each variable creates an input handle
    • Variables are used in filter expressions
    • Built-in spaceIdentityId always available

filter

  • Type: Filter object
  • Operators: eq, ne, gt, gte, lt, lte, contains, startsWith, endsWith, in, notIn, isNull, isNotNull
  • Notes:

    • Supports nested AND/OR logic
    • Property values can be literals or context values
    • Relationship filters supported

limit

  • Type: number
  • Default: 25
  • Minimum: 0
  • Maximum: 1000 (hard cap)
  • Notes:

    • Values are clamped to the range [0, 1000]. A configured limit of 5000 is silently reduced to 1000; a negative value is clamped to 0.
    • When firstOnly is true, the effective limit is 1 regardless of the configured value.
    • To process more than 1000 Records, use Pagination or Patterns for Large Datasets described below.

Selecting Fields, Aggregates and Groups

Without select or groupBy, the node returns whole Records. Set select (and optionally groupBy) to return rows of a chosen shape instead. The shape is the same as the select and groupBy of Record Queries, with the same rules. For how it works, see The Record Query Shape.

  • select is an ordered list of entries. Each result row has one key per entry, its output name (alias), in select order:

    • a field ({ "kind": "field", "id", "path", "alias"? }, or a path string as shorthand) returns a property or a standard property (id, createdAt, …);
    • an aggregate ({ "kind": "aggregate", "id", "alias", "fn", "path"?, "filter"?, "over"? }) returns count, sum, avg, min or max. With over: { "relationshipId", "direction" } it aggregates the Records linked to each returned Record through that relationship (a rollup); direction is the side of the relationship the aggregated Records are on. filter limits the aggregated Records (for example, a count of only the released items). An aggregate filter takes literal values only: it cannot use the node's variables or spaceIdentityId;
    • a group key ({ "kind": "group", "id", "groupBy", "alias"? }, or the group key's alias as shorthand) returns a key of each group.
  • groupBy (up to 4 keys) makes the query grouped: each row is one group of the Records matching filter. A key groups by a string, number, boolean, date or duration property (or createdAt, updatedAt, createdById, updatedById), or, with over, by the Records linked through a relationship; bucket (day, week, month, quarter, year) groups a date by calendar period. A grouped query selects only group keys and aggregates without over, which aggregate each group's Records.

How the node's other settings apply to a shaped query:

Setting

With select/groupBy

filter

Selects the Records (before grouping). Variables are populated as usual.

orderBy / order

Sorts the rows. Defaults to createdAt descending for Records. orderBy can also be the alias of a top-level aggregate when no property has that name, which sorts by the aggregate computed for every matching Record. A grouped query sorts by its group keys or aggregates, and orderBy must be a group key path or a top-level aggregate alias.

limit

Default 25. At most 100 when aggregates are selected, 1000 groups in a grouped query. Not clamped: a larger value fails the node.

offset

Skips rows (or groups).

firstOnly

Returns the first row, or null when there is none.

The results output is typed from the shape: each field has its property's type, a count is a number, a sum/avg has the type of the number or duration it reads, and min/max the type of the property it reads. Values may be null (an unset field, an aggregate with nothing to aggregate, the group of unset values). Changing the shape changes the output type: existing edges stay connected and downstream nodes take the new type, but an edge is only type-checked when it is connected, so check the nodes that read results after changing the shape.

Like a whole-Record query, a shaped query runs with the Space's authority, not as the user who started the run: role filters do not apply, and relationship aggregates count every live linked Record. Only the queried Schema's Space is read: a relationship must link two live Schemas of that Space. Removed Records and removed links are never counted.

In Studio, choose Selected fields, aggregates and groups under Output in the node's details, then add group keys, fields, aggregates and records columns in the Output window (reopen it with Edit output). A records column lists the first linked Records of each row, or the first Records of each group, with the fields, sort and limit you choose. Aggregates over a relationship pick the relationship by its name on the queried Schema; Only include records where… sets an aggregate's filter.

Count Related Records

The number of items in each release, and how many of them are released:

{
  "type": "object:query",
  "data": {
    "label": "Release item counts",
    "schemaId": "release-schema-id",
    "orderBy": "title",
    "order": "asc",
    "select": [
      "title",
      {
        "kind": "aggregate",
        "id": "items",
        "alias": "items",
        "fn": "count",
        "over": { "relationshipId": "in-release-relationship-id", "direction": "from" }
      },
      {
        "kind": "aggregate",
        "id": "released",
        "alias": "released",
        "fn": "count",
        "over": { "relationshipId": "in-release-relationship-id", "direction": "from" },
        "filter": { "type": "property", "path": "status", "operator": "eq", "value": "released" }
      }
    ]
  }
}

The relationship links Items (from) to Releases (to), so the aggregated Items are on the from side. results:

[
  { "title": "R1", "items": 3, "released": 2 },
  { "title": "R2", "items": 0, "released": 0 }
]

Count Records by Status and by Month

{
  "type": "object:query",
  "data": {
    "label": "Items per status",
    "schemaId": "item-schema-id",
    "groupBy": [{ "id": "status", "path": "status" }],
    "select": ["status", { "kind": "aggregate", "id": "count", "alias": "count", "fn": "count" }]
  }
}

results: [{ "status": "draft", "count": 2 }, { "status": "released", "count": 5 }].

{
  "type": "object:query",
  "data": {
    "label": "Items per month",
    "schemaId": "item-schema-id",
    "orderBy": "createdAt",
    "order": "desc",
    "groupBy": [{ "id": "month", "alias": "month", "path": "createdAt", "bucket": "month" }],
    "select": [
      "month",
      { "kind": "aggregate", "id": "count", "alias": "count", "fn": "count" },
      { "kind": "aggregate", "id": "points", "alias": "points", "fn": "sum", "path": "points" }
    ]
  }
}

results: [{ "month": "2026-02-01", "count": 4, "points": 21 }, { "month": "2026-01-01", "count": 3, "points": 8 }]. A month is the date of its first day; a date-time is placed in a month in the run's timezone (the timezone preference of the user who started it), or UTC.

Compared With List - Aggregate

A shaped query aggregates on the server, over every matching Record, without loading them into the run. List - Aggregate is the node for aggregating a list the run already holds, such as the output of another node or an HTTP response.

Errors

A shape that a Record query rejects (an unknown property, a field in a grouped query, a relationship of another Schema, a limit over the limits above) fails the node with INVALID_QUERY and the reason. A filter that cannot be evaluated matches nothing, as in a Record query, rather than failing with INVALID_FILTER.

Result Limits

The Object - Query node enforces a hard cap on the number of Records a single run can return.

Limit Reference

Setting

Value

Notes

Default

25

Applied when limit is not configured

Minimum

0

Negative values are clamped to 0

Maximum

1000

Configured values above 1000 are clamped

firstOnly: true

1

Overrides the configured limit

For result sets larger than ~1000 Records, a single run of the Object - Query node will not return them all. See the patterns below.

Pagination

The Object - Query node supports offset + limit pagination. Both are configured directly on the node.

Behaviour

Detail

First page

offset: 0, limit: N returns Records 1 through N.

Subsequent pages

offset: N, limit: N returns Records N+1 through 2N; offset: 2N returns 2N+1 through 3N; and so on.

End-of-results detection

When the returned results array length is less than the configured limit, no further pages are available. Stop the pagination loop on this condition.

Stable ordering

Without a stable sort, offset pages may overlap or skip Records. Sort with orderBy to get a stable order.

Concurrent writes

offset is positional, not snapshot-isolated. Records created or removed during pagination can cause duplicates or gaps. Filter narrowing (for example, a createdAt <= startedAt clause) can anchor the window; see Filter-Narrow Before Query.

Cursor-based pagination tokens

Not supported. The node does not emit a nextCursor token. End-of-results must be detected by comparing results.length to the requested limit as above.

Patterns for Large Datasets

1. Pagination With offset + limit

Each iteration advances offset by limit; a page with fewer than limit results is the last page.

{
  "type": "object:query",
  "data": {
    "label": "Page through orders",
    "schemaId": "order-schema-uuid",
    "variables": [{ "id": "pageOffset", "type": { "type": "number", "required": true } }],
    "orderBy": "createdAt",
    "order": "asc",
    "limit": 1000,
    "offset": 0
  }
}

The pageOffset variable supplies offset from the surrounding Flow. A page with fewer than limit results is the last page (see Pagination).

2. Filter-Narrow Before Query

Selective filters (properties, time windows, status) can reduce the candidate set so that it fits within 1000 Records.

{
  "type": "object:query",
  "data": {
    "label": "Yesterday's failed orders",
    "schemaId": "order-schema-uuid",
    "filter": {
      "type": "and",
      "filters": [
        {
          "path": "status",
          "type": "property",
          "value": { "type": "literal", "value": "failed" },
          "operator": "eq"
        },
        {
          "path": "createdAt",
          "type": "property",
          "value": { "type": "contextValue", "valueId": "sinceTimestamp" },
          "operator": "gte"
        }
      ]
    },
    "orderBy": "createdAt",
    "order": "desc",
    "limit": 500
  }
}

3. Aggregate With List - Aggregate

When the goal is a count, sum, or other reduction rather than the rows themselves, use a groupBy shape for counts and totals per group, or an aggregate over a relationship for each Record's related Records: the server aggregates every matching Record, however many there are. List - Aggregate remains the node for aggregating a list already in the run.

Behaviour

Execution Flow

  1. Resolves all variable values from input handles
  2. Populates filter with variable values
  3. Matches Records of the configured Schema in the current Space, excluding removed Records
  4. Applies filter conditions, ordering, and pagination
  5. Augments file entries with context
  6. Returns results (single or array)

Error Handling

Scenario

Behaviour

Schema not found

The run fails with error code MISSING_SCHEMA

Invalid filter

The run fails with error code INVALID_FILTER

Invalid select/groupBy shape

The run fails with error code INVALID_QUERY

No results

Returns null (firstOnly) or empty array

Examples

Query by ID

{
  "type": "object:query",
  "data": {
    "label": "Get User by ID",
    "schemaId": "user-schema-uuid",
    "variables": [
      {
        "id": "userId",
        "type": { "name": "userId", "type": "string", "required": true }
      }
    ],
    "filter": {
      "path": "id",
      "type": "property",
      "value": { "type": "contextValue", "valueId": "userId" },
      "operator": "eq"
    },
    "firstOnly": true
  }
}

Query With Pagination

{
  "type": "object:query",
  "data": {
    "label": "List Recent Orders",
    "schemaId": "order-schema-uuid",
    "filter": {
      "path": "status",
      "type": "property",
      "value": { "type": "literal", "value": "completed" },
      "operator": "eq"
    },
    "orderBy": "createdAt",
    "order": "desc",
    "limit": 10,
    "offset": 0
  }
}

Search Products With Combined Filters

{
  "type": "object:query",
  "data": {
    "label": "Search Products",
    "schemaId": "product-schema-uuid",
    "variables": [
      { "id": "minPrice", "type": { "type": "number" } },
      { "id": "category", "type": { "type": "string" } }
    ],
    "filter": {
      "type": "and",
      "filters": [
        {
          "path": "price",
          "type": "property",
          "value": { "type": "contextValue", "valueId": "minPrice" },
          "operator": "gte"
        },
        {
          "path": "category",
          "type": "property",
          "value": { "type": "contextValue", "valueId": "category" },
          "operator": "eq"
        },
        {
          "path": "active",
          "type": "property",
          "value": { "type": "literal", "value": true },
          "operator": "eq"
        }
      ]
    },
    "limit": 50
  }
}

Related pages

Page not found. It may have moved or may not be published.
Browse documentation