Reading data
Queries that stay live: filters, ordering, pagination and nested links.
db client (@domainruntime/react) is planned; the block below shows the client you use today.You read with a query: an object that names what you want. The result has the same shape.
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: hidden objects are left out without an error.
Today
Use the InstantDB client pointed at your environment:
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 UsersQueries, useQuery and subscribeQuery work as on this page. Writes do not: the environment rejects client transactions.
Filtering
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 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:
db.useQuery({
orders_order: {
$: {
where: {
or: [{ status: "open" }, { and: [{ status: "held" }, { total: { $gt: 1000 } }] }],
},
},
},
});Ordering and limits
db.useQuery({
orders_order: { $: { order: { placedAt: "desc" }, limit: 20 } },
});order needs an indexed, typed attribute.
Pagination
Cursors stay stable while data changes underneath. Use them for lists people scroll through:
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.startCursorpageInfo.<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
Follow links by nesting them:
db.useQuery({
orders_customer: {
$: { where: { email: "a@acme.com" } },
orders: {
$: { order: { placedAt: "desc" }, limit: 5 },
invoices: {},
},
},
});
// data.orders_customer[0].orders[0].invoicesEach level takes its own $ options.
Selecting fields
db.useQuery({ orders_order: { $: { fields: ["status", "total"] } } });Outside React
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
Actions read with runtime.query, under the caller's permissions:
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
- Actions — changing what you read.
- Permissions — deciding what each user can read.