> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Lua Query

> The bounded filter grammar accepted by Data.get() and Products.get()

Lua Query is the filter object [`Data.get()`](/reference/sdk/data#get) and [`Products.get({ filter })`](/reference/sdk/products#get) accept: a bounded, Mongo-style grammar with nine field operators and root-level `$and` and `$or`. The platform compiles it against each entry's `data` fields only, so a filter can never reach ownership fields such as the agent or the end user. Anything outside the grammar is rejected with a `FILTER_*` error before the query runs.

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import type { LuaQuery, LuaQueryFieldOperators, LuaQueryScalar } from 'lua-cli';
```

## Quick example

```ts theme={null}
import { Data } from 'lua-cli';
import type { LuaQuery } from 'lua-cli';

const filter: LuaQuery = {
  $and: [
    { status: { $in: ['open', 'pending'] } },
    { 'account.tier': 'enterprise' },
    { assignee: { $exists: false } },
  ],
};
const page = await Data.get('tickets', filter, 1, 50);
```

## Grammar

### Field predicates

| Form                          | Meaning                                | Example                                                             |
| ----------------------------- | -------------------------------------- | ------------------------------------------------------------------- |
| `{ field: scalar }`           | Equality                               | `{ status: 'open' }`                                                |
| `{ field: [scalar, ...] }`    | Shorthand for `$in`                    | `{ tags: ['urgent', 'vip'] }`                                       |
| `{ field: { $op: operand } }` | One or more operators on the field     | `{ price: { $gte: 10, $lt: 100 } }`                                 |
| `{ field: { child: ... } }`   | Nested object; same as the dotted path | `{ address: { city: 'Oslo' } }` equals `{ 'address.city': 'Oslo' }` |

A scalar is a string, a finite number, a boolean, or `null`.

### Operators

| Operator  | Operand          | Meaning                 |
| --------- | ---------------- | ----------------------- |
| `$eq`     | scalar           | Equal                   |
| `$ne`     | scalar           | Not equal               |
| `$gt`     | scalar           | Greater than            |
| `$gte`    | scalar           | Greater than or equal   |
| `$lt`     | scalar           | Less than               |
| `$lte`    | scalar           | Less than or equal      |
| `$in`     | array of scalars | Any of the values       |
| `$nin`    | array of scalars | None of the values      |
| `$exists` | boolean          | Field present or absent |

### Logical operators

`$and` and `$or` take a non-empty array of filters, each compiled with the same rules. They are valid at the root of a filter and inside another logical branch, never under a field: `{ price: { $or: [...] } }` is rejected.

### Rules

* An operator object holds operators only; mixing an operator with a field key (`{ price: { $gt: 1, currency: 'USD' } }`) is rejected.
* A field path is one or more dotted segments. A segment may not be empty, start with `$`, contain a null byte, or be `__proto__`, `prototype`, or `constructor`.
* Two keys that compile to the same path (`{ a: { b: 1 }, 'a.b': 2 }`) are rejected.
* An empty nested object is rejected.
* Every operator outside the nine listed, including `$regex`, `$where`, `$expr`, `$text`, `$not`, `$nor`, and `$elemMatch`, is rejected.

## Limits

| Limit                        | Maximum         |
| ---------------------------- | --------------- |
| Encoded filter size          | 8,192 bytes     |
| Nesting depth                | 8               |
| Nodes in the filter          | 128             |
| Branches per `$and` or `$or` | 20              |
| Values per `$in` or `$nin`   | 100             |
| Field path                   | 256 bytes       |
| String operand               | 4,096 bytes     |
| Pagination offset            | 100,000 entries |
| Query execution time         | 5,000 ms        |

## Errors

A rejected filter fails the call with one of these codes and the JSON path of the offending key.

| Code                                | Cause                                                                                |
| ----------------------------------- | ------------------------------------------------------------------------------------ |
| `FILTER_INVALID_JSON`               | The filter isn't valid JSON                                                          |
| `FILTER_ROOT_MUST_BE_OBJECT`        | A filter or branch isn't a JSON object                                               |
| `FILTER_UNSUPPORTED_OPERATOR`       | An operator outside the grammar, or a logical operator under a field                 |
| `FILTER_INVALID_OPERATOR_PLACEMENT` | An operator object also contains field keys                                          |
| `FILTER_INVALID_FIELD`              | A field path is empty, too long, or has a reserved segment                           |
| `FILTER_INVALID_OPERAND`            | A non-scalar operand, a non-array `$in`, a non-boolean `$exists`, or an empty branch |
| `FILTER_DUPLICATE_PATH`             | Two keys compile to the same path                                                    |
| `FILTER_CYCLE`                      | The object references itself                                                         |
| `FILTER_TOO_LARGE`                  | Over 8,192 bytes                                                                     |
| `FILTER_TOO_DEEP`                   | Deeper than 8 levels                                                                 |
| `FILTER_TOO_COMPLEX`                | More than 128 nodes                                                                  |
| `FILTER_TOO_MANY_BRANCHES`          | More than 20 branches in one logical operator                                        |
| `FILTER_TOO_MANY_VALUES`            | More than 100 values in one list                                                     |

## Types

The three exported types describe the shape; placement and limits are enforced when the query runs.

```ts theme={null}
import type { LuaQuery, LuaQueryFieldOperators, LuaQueryScalar } from 'lua-cli';

const scalar: LuaQueryScalar = 'open';
const range: LuaQueryFieldOperators = { $gte: 10, $lt: 100 };
export const query: LuaQuery = { status: scalar, price: range, tags: ['urgent', 'vip'] };
```

<ResponseField name="LuaQueryScalar" type="string | number | boolean | null">
  An operand or an equality value.
</ResponseField>

<ResponseField name="LuaQueryFieldOperators" type="object">
  Optional `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte` (scalar), `$in`, `$nin` (readonly scalar array), and `$exists` (boolean).
</ResponseField>

<ResponseField name="LuaQuery" type="Record<string, LuaQueryValue>">
  Each key is a field path or a root logical operator; each value is a scalar, a scalar array, a `LuaQueryFieldOperators`, a nested `LuaQuery`, or an array of `LuaQuery` branches.
</ResponseField>

## See also

* [`Data`](/reference/sdk/data) — `get()` takes a Lua Query
* [`Products`](/reference/sdk/products) — `get({ filter })` takes a Lua Query
* [Store and search data](/build/store-and-search-data) — how-to
* [Custom data REST API](/reference/rest/custom-data) — the same filter as a query-string parameter
