Querying
Filters, sorting, pagination, relations, counts and aggregates
Read rows
const todos = await db.orm.todo.all();all() returns every matching row. You can await it for an array, call .toArray(), or iterate it:
for await (const todo of db.orm.todo.where({ done: false }).all()) {
console.log(todo.title);
}first() returns the first matching row, or null:
const next = await db.orm.todo.where({ done: false }).orderBy({ title: "asc" }).first();findUnique() reads one row by primary key and returns null when there's none. For a compound key, pass the values as an array, in the order the key declares them:
const user = await db.orm.user.findUnique("user_1");
const member = await db.orm.membership.findUnique(["org_1", "user_1"]); // @@id([orgId, userId])Filtering
Pass an object to match fields by equality. Every field must match:
const mine = await db.orm.todo.where({ done: false, userId: "user_1" }).all();
const noEmail = await db.orm.user.where({ email: null }).all(); // email is nullPass a function to use operators. It receives each field of the model:
const releases = await db.orm.todo.where((todo) => todo.title.contains("release")).all();| Operator | Matches when the field… |
|---|---|
eq(v), neq(v) | equals, or doesn't equal, v. |
gt(v), gte(v), lt(v), lte(v) | is greater or less than v. |
in(values), notIn(values) | is, or isn't, one of values. |
contains(s), startsWith(s), endsWith(s) | is a string containing, starting or ending with s. Case-sensitive. |
isNull(), isNotNull() | is, or isn't, null. |
Combine conditions with and, or and not from @prisma-idb/client-idb/orm:
import { and, not, or } from "@prisma-idb/client-idb/orm";
const todos = await db.orm.todo
.where((todo) => or(todo.title.contains("release"), and(todo.done.eq(false), not(todo.userId.eq("user_1")))))
.all();Calling where() more than once adds conditions: a row must match all of them.
DateTime and Bytes values compare by value, so eq(new Date("2026-01-01")) matches a stored date at that moment. Comparing values of different types follows IndexedDB's key order: numbers, then dates, then strings, then binary values.
Equality filters on an indexed field or the primary key use the index. Other filters read every row in the store and test each one.
Sorting and pagination
const page = await db.orm.todo.orderBy({ title: "asc" }).skip(20).take(10).all();orderBytakes one or more fields, each"asc"or"desc". Later fields break ties. CallingorderByagain replaces the sort.nullsorts after every other value, or before them when descending.- Without
orderBy, rows come back in primary-key order, or index order when the query uses an index.
Select
Return only some scalar fields. The result type narrows to match:
const titles = await db.orm.todo.select("id", "title").all();
// { id: string; title: string }[]Include relations
include() adds a related model to each row. A to-one relation becomes a row or null; a to-many relation becomes an array.
const todos = await db.orm.todo.include("user").all(); // todo.user: User | null
const users = await db.orm.user.include("todos").all(); // user.todos: Todo[]Pass a function to filter, sort or limit a to-many relation. These apply per parent, so take(5) gives each user up to five todos:
const users = await db.orm.user.include("todos", (todos) => todos.where({ done: false }).take(5)).all();Return count() from the function to get the number of related rows instead:
const users = await db.orm.user.include("todos", (todos) => todos.count()).all(); // user.todos: numberEach included relation is loaded with one batched read, however many parent rows there are.
Count
const open = await db.orm.todo.where({ done: false }).count();count() applies skip and take, unlike SQL's COUNT. To get the total for a paginated list, count before adding them.
Aggregates
const stats = await db.orm.task.aggregate((agg) => ({
total: agg.count(),
avgPriority: agg.avg("priority"),
maxPriority: agg.max("priority"),
}));count() counts rows. sum, avg, min and max take a numeric field.
Group by
const perUser = await db.orm.todo.groupBy("userId").aggregate((agg) => ({ total: agg.count() }));
// [{ userId: "user_1", total: 3 }, { userId: "user_2", total: 1 }]groupBy() returns one row per distinct combination of the grouping fields, with the aggregates you ask for. It supports BigInt fields. Equal Date, binary, array and JSON object values share a group. A Date and its ISO string form separate groups. Invalid Dates share a separate group. Null and undefined share a group.