Prisma ORM 8 is here.Read the docs

Reading data

Fetch one record or many with Prisma ORM, then filter, select, sort, paginate, and iterate the results.

This page shows how to read data with Prisma ORM: fetching many records or one, filtering, selecting fields, sorting and paginating, counting, and iterating large results.

Every query chains methods on a model. The last call in the chain says what you want back and runs the query. Usually that is .all() or .first():

import { db } from "./prisma/db";

// Every published post
const posts = await db.orm.public.Post.where({ published: true }).all();

// One user, or null
const user = await db.orm.public.User.where({ email: "alice@prisma.io" }).first();

npm create prisma@latest scaffolds a new project. npx prisma orm init adds Prisma ORM to a project you already have.

Your contract is the contract.prisma file that replaced schema.prisma. It is where you declare your models, and every result on this page is typed from it. See Core concepts for how the contract and the client fit together.

db is the client you create once in src/prisma/db.ts. npx prisma orm init writes that file for you. Commit it; unlike Prisma ORM 7's generated client, it is your code. db.ts reads two generated files that npx prisma contract emit writes next to it; you do not edit those. These examples are written from a file in src/, so the import is ./prisma/db; from elsewhere, change the relative path.

db.orm holds your models. On PostgreSQL you reach a model through the database schema it is in: in db.orm.public.User, User is the model name and public is the PostgreSQL schema. It is public unless the model sits in a namespace block in your contract; see Coming from Prisma ORM 7. On MongoDB there is no schema segment, and the name is the collection: db.orm.users.

You can write .where, .select, .orderBy, .limit, and .offset in any order. .cursor(...) must come after .orderBy(...).

For Prisma ORM 7 users, findMany and findFirst / findUnique map directly onto .all() and .first() (Coming from Prisma ORM 7 maps the rest of the API):

- const posts = await prisma.post.findMany({ where: { published: true } });
+ const posts = await db.orm.public.Post.where({ published: true }).all();

- const user = await prisma.user.findUnique({ where: { email } });
+ const user = await db.orm.public.User.where({ email }).first();

- const page = await prisma.post.findMany({ take: 20, skip: 20 });
+ const page = await db.orm.public.Post.limit(20).offset(20).all();

.first() asks the database for one row. findUnique and findFirst both become .first(), so if several records match you get one of them, in no guaranteed order; add .orderBy(...) to choose.

Example schema

All examples on this page are based on the contract below. The file is contract.prisma, and it is still written in the Prisma schema language, so it looks like the schema.prisma you already know. @default(cuid(2)) generates a CUID version 2 id.

Expand for sample schema
model User {
  id        String   @id @default(cuid(2))
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  posts     Post[]
}

model Post {
  id        String   @id @default(cuid(2))
  title     String
  content   String?
  published Boolean
  authorId  String
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
}

Fetch many records or one

Use .all() when you want every matching record. It returns an array:

const users = await db.orm.public.User.all();
[
  { id: 'cuid20000000000000000001', email: 'alice@prisma.io', name: 'Alice', createdAt: 2026-07-06T09:03:13.808Z },
  { id: 'cuid20000000000000000002', email: 'bob@prisma.io', name: 'Bob', createdAt: 2026-07-06T09:03:14.112Z }
]

.all() takes no filter of its own. Put the filter in .where(...) before it. .all() also applies no limit, so combine it with limit on tables that can grow.

Use .first() when you want a single record. It returns the record, or null when nothing matches. On both databases it asks for at most one row:

const user = await db.orm.public.User.where({ email: "alice@prisma.io" }).first();
{ id: 'cuid20000000000000000001', email: 'alice@prisma.io', name: 'Alice', createdAt: 2026-07-06T09:03:13.808Z }

On PostgreSQL you can skip .where(...) and pass the filter straight to .first(...). It takes anything .where(...) takes, not just the primary key. On MongoDB, .first() takes no argument, so filter with .where(...):

const user = await db.orm.public.User.first({ id: userId });

If you want an error instead of null when nothing matches, call .firstOrThrow(). This is what replaces findUniqueOrThrow and findFirstOrThrow:

const user = await db.orm.public.User.where({ email }).limit(1).all().firstOrThrow();

.limit(1) keeps it to one row. .firstOrThrow() throws an error with code RUNTIME.NO_ROWS when nothing matches. There is no .firstOrThrow() on .first(). The error is an Error with a code property, so check "code" in error before you read it. The same form works on MongoDB, with db.orm.users.

Filter records

Use .where(...) to narrow a query. Pass an object to match fields by equality:

const drafts = await db.orm.public.Post.where({ published: false }).all();

Chain several .where(...) calls to combine conditions with AND. This is also how you express a range:

const recentPosts = await db.orm.public.Post
  .where((p) => p.createdAt.gte(start))
  .where((p) => p.createdAt.lte(end))
  .all();

Filter operators on PostgreSQL

On PostgreSQL, .where(...) also accepts a callback for richer comparisons, as in the range example above. The callback receives one object, written p here, with a property per field of your model. Each of those fields has .eq, .neq, .lt, .lte, .gt, .gte, .like, .ilike, .in([...]), .notIn([...]), .isNull(), and .isNotNull(). .like and .ilike take SQL LIKE patterns, where % matches any run of characters:

// Case-insensitive text search
const matchingPosts = await db.orm.public.Post
  .where((p) => p.title.ilike("%prisma%"))
  .all();

// One of several values
const team = await db.orm.public.User
  .where((u) => u.email.in(["alice@prisma.io", "bob@prisma.io"]))
  .all();

To combine conditions with OR, AND, or NOT, use the or, and, and not helpers from @prisma/orm-postgres/orm-client. @prisma/orm-postgres is already installed; it is the package db.ts imports from:

import { and, not, or } from "@prisma/orm-postgres/orm-client";

const highlighted = await db.orm.public.Post
  .where((p) => or(p.title.ilike("%hello%"), p.title.ilike("%prisma%")))
  .all();

const publishedPrismaPosts = await db.orm.public.Post
  .where((p) => and(p.published.eq(true), p.title.ilike("%prisma%")))
  .all();

const notHello = await db.orm.public.Post
  .where((p) => not(p.title.eq("Hello")))
  .all();

Filter operators on MongoDB

On MongoDB, .where(...) does not take a callback. The object form covers equality:

const drafts = await db.orm.posts.where({ published: false }).all();

For anything else, pass .where(...) a filter built with MongoFieldFilter, imported from @prisma/orm-mongo/query-ast/execution. @prisma/orm-mongo is the package db.ts imports from. MongoFieldFilter has one static method per operator: eq, neq, gt, gte, lt, lte, in, nin (not in), isNull, and isNotNull. Each one takes the field name first:

import { MongoFieldFilter } from "@prisma/orm-mongo/query-ast/execution";

const junePosts = await db.orm.posts
  .where(MongoFieldFilter.gte("createdAt", new Date("2026-06-01")))
  .where(MongoFieldFilter.lt("createdAt", new Date("2026-07-01")))
  .all();

Chained .where(...) calls combine with AND, the same as on PostgreSQL. Call .not() on a filter to invert it, and use MongoOrExpr.of([...]) from the same import to combine filters with OR:

import { MongoFieldFilter, MongoOrExpr } from "@prisma/orm-mongo/query-ast/execution";

const notAlice = await db.orm.users
  .where(MongoFieldFilter.eq("name", "Alice").not())
  .all();

const oldOrNew = await db.orm.posts
  .where(
    MongoOrExpr.of([
      MongoFieldFilter.eq("title", "Old"),
      MongoFieldFilter.eq("title", "New"),
    ]),
  )
  .all();

MongoFieldFilter has no case-insensitive or partial-match operator. For those, use the pipeline builder, which has a regexMatch expression.

The full operator list is in Filter conditions and operators.

Select fields

Use .select(...) to fetch only the fields you need. On PostgreSQL the result type narrows to match. On MongoDB only the fetched document narrows; the type keeps every field, so a field you left out is undefined at runtime. On both, you can filter on fields you did not select:

const users = await db.orm.public.User.select("id", "email").all();
[
  { id: 'cuid20000000000000000001', email: 'alice@prisma.io' },
  { id: 'cuid20000000000000000002', email: 'bob@prisma.io' }
]

Sort and paginate

Use .orderBy(...) to sort, .limit(n) to limit, and .offset(n) to offset.

On PostgreSQL, sort with a callback that calls .asc() or .desc() on a field. On MongoDB, sort with MongoDB's own direction numbers: 1 for ascending and -1 for descending.

// Second page of posts, newest first
const page = await db.orm.public.Post
  .orderBy((p) => p.createdAt.desc())
  .limit(20)
  .offset(20)
  .all();

For a composite sort on PostgreSQL, pass an array of callbacks. Records are sorted by the first field, with the second as tiebreaker:

const posts = await db.orm.public.Post
  .orderBy([(p) => p.createdAt.desc(), (p) => p.id.desc()])
  .all();

Cursor pagination

Offset gets slower on deep pages. For stable pagination over a large table, follow .orderBy(...) with .cursor(...) and resume from the last record you returned. .cursor(...) is PostgreSQL only.

The cursor record is excluded from the next page. Pages pick up strictly after it.

Pass .cursor(...) a value for every field you sorted by, as in the example below. Keep the id tiebreaker in both the sort and the cursor. createdAt is not unique, and a cursor on a non-unique field alone can skip or repeat records that share the boundary value. With id in the cursor, pages never overlap even when timestamps tie:

const page1 = await db.orm.public.Post
  .orderBy([(p) => p.createdAt.desc(), (p) => p.id.desc()])
  .limit(20)
  .all();

const last = page1[page1.length - 1]!;
const page2 = await db.orm.public.Post
  .orderBy([(p) => p.createdAt.desc(), (p) => p.id.desc()])
  .cursor({ createdAt: last.createdAt, id: last.id })
  .limit(20)
  .all();

On MongoDB, page with .limit(n) and .offset(n).

Count records

On PostgreSQL, count with .aggregate(...). Like .all() and .first(), it is the last call in the chain: it says what you want back and runs the query. It takes a callback, written a here, and returns an object with the keys you named:

const result = await db.orm.public.Post
  .where({ published: true })
  .aggregate((a) => ({ total: a.count() }));
{ total: 2 }

The callback offers a.count(), a.sum(...), a.avg(...), a.min(...), and a.max(...), plus countBigInt(), sumBigInt(...), and avgDecimal(...) for values beyond a JavaScript number. All but a.count() take a field name as a string, such as a.max("createdAt"). Ask for as many as you like in one call, one key each:

const stats = await db.orm.public.Post
  .where({ published: true })
  .aggregate((a) => ({ total: a.count(), newest: a.max("createdAt") }));

There is no .count() method on the query chain, on either database.

MongoDB has no .count() and no .aggregate(...). Count with the pipeline builder, db.query: .from("posts") names the collection, .count("total") counts, .build() finishes the query, and (await db.runtime()).query(...) runs it (on MongoDB db.runtime() returns a promise).

import { db } from "./prisma/db";

const built = db.query
  .from("posts")
  // .match((f) => f.published.eq(true)) counts only a subset
  .count("total")
  .build();

const [result] = await (await db.runtime()).query(built);
{ total: 2 }

Iterate a large result

You can await a read for an array, or loop over it with for await. Pick one; once you iterate a result with for await it is used up (see below).

await runs the query and gives you an array. This is the right default. You get the whole result in memory and can read the array as often as you like.

const posts = await db.orm.public.Post.all();

console.log(posts.length);
console.log(posts[0]);

Use for await to handle records one at a time, as your loop asks for them, for example to write each one to a file:

for await (const post of db.orm.public.Post.all()) {
  await exportToSearchIndex(post);
}

It does not fetch less: on the standard client (postgres({...}) in src/prisma/db.ts) every row is loaded first. For large tables page with .limit() and .cursor() (see Sort and paginate).

The serverless client, postgresServerless(...), does fetch rows as you iterate, but it has no db.orm, so none of the queries on this page run on it: see Transactions and runtime.

An iterated result can only be read once

Once a for await loop has touched a result, that result is finished, even if the loop exited early. Iterating it again, or awaiting it afterwards, throws:

const result = db.orm.public.Post.all();

for await (const post of result) {
  // ...
}

await result;
RuntimeError: AsyncIterableResult iterator has already been consumed via for-await loop.
Each AsyncIterableResult can only be iterated once.

AsyncIterableResult is the type a query returns before you await it; the error means you read it twice. The error has the code RUNTIME.ITERATOR_CONSUMED.

If you need the data more than once, await the query into an array and reuse the array:

const posts = await db.orm.public.Post.all();

const published = posts.filter((p) => p.published);
const titles = posts.map((p) => p.title);

The read-once rule is the same on PostgreSQL and MongoDB.

Common mistakes

Fetching everything to use one record

You wanted one record, so you queried and took the first element:

const users = await db.orm.public.User.where({ email }).all();
const user = users[0];

This fetches every matching record and throws away the rest. Use .first() instead. It returns one record or null, and it asks the database for at most one row:

const user = await db.orm.public.User.where({ email }).first();

Forgetting that .all() has no limit

.all() returns every match. On a table that grows, yesterday's fast query becomes today's slow one. Add .limit(n) when you don't genuinely need every record. When you do need every record, page through the table: .cursor(...) on PostgreSQL, .offset(n) on MongoDB.

Reusing an iterated result

You read a result with for await, then tried to read it again. The second read throws, because a result is used up as it is iterated. Store the data if you need it twice:

const posts = await db.orm.public.Post.all();
// posts is a plain array now; read it as often as you like

Prompt your coding agent

Projects created with npm create prisma@latest include the Prisma ORM skills for your coding agent; in an existing project, run npx prisma skills sync. Skills are instruction files a coding agent reads, and the prisma-8 skill covers everything on this page. Prompts that map to each section:

  • "Using the prisma-8 skill, write a query that returns the 20 newest published posts."
  • "Add a case-insensitive title search to the posts query, using the .ilike operator."
  • "Convert this offset pagination to cursor pagination with the .cursor() API."
  • "This export loops over a huge table. Rewrite it to page through the rows with .limit() and .cursor()."

Next

On this page