# Reading data

URL: https://docs.domainruntime.dev/docs/data/reading-data
Status: Guide
Reviewed: 2026-09-22



<div className="docs-note">
  The query language on this page runs in production today. The 

  `db`

   client (

  `@domainruntime/react`

  ) is 

  **planned**

  ; the block below shows the client you use today.
</div>

You read with a query: an object that names what you want. The result has the same shape.

```tsx
const { isLoading, error, data } = db.useQuery({ orders_order: {} });
// data → { orders_order: [{ id: "…", status: "open", total: 120, placedAt: … }, …] }
```

`useQuery` is live. When any matching object changes — from this tab, another user, or an action — the component re-renders with the new result.

If a query returns nothing, check the entity's `view` [rule](/docs/auth/permissions): hidden objects are left out without an error.

<details>
  <summary>
    Today
  </summary>

  Use the InstantDB client pointed at your environment:

  ```ts
  import { init } from "@instantdb/react";
  import schema from "@/instant.schema";

  const env = process.env.NEXT_PUBLIC_DOMAIN_RUNTIME_URL!;   // https://<handle>.domainruntime.cloud
  export const db = init({
    appId: process.env.NEXT_PUBLIC_DOMAIN_ENVIRONMENT_ID!,
    apiURI: env,
    websocketURI: `${env.replace(/^http/, "ws")}/session`,
    schema,
  });
  await db.auth.signInWithToken(userToken);   // see Users
  ```

  Queries, `useQuery` and `subscribeQuery` work as on this page. Writes do not: the environment rejects client transactions.
</details>

## Filtering [#filtering]

```ts
db.useQuery({ orders_order: { $: { where: { status: "open" } } } });
```

| Filter                                  | Matches                                                                                                       |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `{ status: "open" }`                    | equal                                                                                                         |
| `{ status: { $ne: "cancelled" } }`      | not equal                                                                                                     |
| `{ status: { $in: ["open", "held"] } }` | any of the values                                                                                             |
| `{ total: { $gt: 100 } }`               | `$gt`, `$gte`, `$lt`, `$lte` — the attribute must be [indexed](/docs/data/modeling-data#attributes) and typed |
| `{ notes: { $isNull: true } }`          | missing value                                                                                                 |
| `{ "owner.id": { $isNull: true } }`     | objects with no linked owner                                                                                  |
| `{ reference: { $like: "ORD-2026%" } }` | a pattern, case-sensitive; `$ilike` ignores case. Indexed attribute.                                          |
| `{ "customer.email": "a@acme.com" }`    | a value on a linked object                                                                                    |
| `{ id: orderId }`                       | one object                                                                                                    |

Several keys in one `where` must all match. For anything else, use `and` and `or`:

```ts
db.useQuery({
  orders_order: {
    $: {
      where: {
        or: [{ status: "open" }, { and: [{ status: "held" }, { total: { $gt: 1000 } }] }],
      },
    },
  },
});
```

## Ordering and limits [#ordering-and-limits]

```ts
db.useQuery({
  orders_order: { $: { order: { placedAt: "desc" }, limit: 20 } },
});
```

`order` needs an indexed, typed attribute.

## Pagination [#pagination]

Cursors stay stable while data changes underneath. Use them for lists people scroll through:

```ts
const { data, pageInfo } = db.useQuery({
  orders_order: { $: { order: { placedAt: "desc" }, first: 20 } },
});

// next page
db.useQuery({
  orders_order: { $: { order: { placedAt: "desc" }, first: 20, after: pageInfo.orders_order.endCursor } },
});
// previous page: last: 20, before: pageInfo.orders_order.startCursor
```

`pageInfo.<entity>` carries `startCursor`, `endCursor`, `hasNextPage` and `hasPreviousPage`.

For a fixed page number, `limit` and `offset` also work: `{ limit: 20, offset: 40 }`. That page shifts if objects are added before it.

## Nested links [#nested-links]

Follow links by nesting them:

```ts
db.useQuery({
  orders_customer: {
    $: { where: { email: "a@acme.com" } },
    orders: {
      $: { order: { placedAt: "desc" }, limit: 5 },
      invoices: {},
    },
  },
});
// data.orders_customer[0].orders[0].invoices
```

Each level takes its own `$` options.

## Selecting fields [#selecting-fields]

```ts
db.useQuery({ orders_order: { $: { fields: ["status", "total"] } } });
```

## Outside React [#outside-react]

```ts
const stop = db.subscribeQuery({ orders_order: {} }, (result) => {
  if (result.data) render(result.data.orders_order);
});

const { data } = await db.queryOnce({ orders_order: { $: { limit: 1 } } });
```

## Inside an action [#inside-an-action]

Actions read with `runtime.query`, under the caller's permissions:

```ts
async execute({ input, runtime }) {
  const { orders_order } = await runtime.query({
    orders_order: { $: { where: { id: input.orderId } }, owner: {} },
  });
}
```

Today: `const db = await runtime.db(); const { orders_order } = await db.query({ … });`

## Next [#next]

* [Actions](/docs/data/actions) — changing what you read.
* [Permissions](/docs/auth/permissions) — deciding what each user can read.
