Skip to main content
Prisma Documentation Docs

Search documentation

Type to search this documentation.

On this pageOverview

Reading data

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, and the last call in the chain says what you want back and runs the query, usually .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();
TypeScript
import { db } from "./prisma/db";

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

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

npm create prisma@latest scaffolds a new project, while 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, and 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, but .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):

diff
- 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.

npm create prisma@latest scaffolds a new project, while 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, and 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, but .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.

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())

}
prisma
model User {
  id        ObjectId @id @map("_id")
  email     String   @unique
  name      String?
  createdAt Date
  posts     Post[]
  @@map("users")
}

model Post {
  id        ObjectId @id @map("_id")
  title     String
  content   String?
  published Bool
  author    User     @relation(fields: [authorId], references: [id])
  authorId  ObjectId
  createdAt Date
  @@map("posts")
}

[!NOTE] On MongoDB, @map("_id") renames the field to _id everywhere: you filter on _id, you select _id, and the returned document has an _id key. That is why the MongoDB examples below never say id.

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, so 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, and 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(...), which 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 });
TypeScript
const user = await db.orm.users.where({ _id: userId }).first();

If you want an error instead of null when nothing matches, call .firstOrThrow(), the replacement for findUniqueOrThrow and findFirstOrThrow:

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

.limit(1) keeps it to one row, and .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.

If you want an error instead of null when nothing matches, call .firstOrThrow(), the replacement for findUniqueOrThrow and findFirstOrThrow:

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

.limit(1) keeps it to one row, and .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.

Use .where(...) to narrow a query, and 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, which 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();

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(), as far as its type supports them: a native enum field (pg.enum(...)), for example, has no .like or .ilike. .like and .ilike take SQL LIKE patterns, where % matches any run of characters. For search over words rather than patterns, text fields also have PostgreSQL full-text search:

// 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();

On MongoDB, .where(...) does not take a callback, but 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, and 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.

Use .select(...) to fetch only the fields you need. On PostgreSQL the result type narrows to match, while 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();
TypeScript
const users = await db.orm.users.select("_id", "email").all();
no-copy
[
  { id: 'cuid20000000000000000001', email: 'alice@prisma.io' },
  { id: 'cuid20000000000000000002', email: 'bob@prisma.io' }
]
[

  { id: 'cuid20000000000000000001', email: 'alice@prisma.io' },

  { id: 'cuid20000000000000000002', email: 'bob@prisma.io' }

]

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, and 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();
TypeScript
// Second page of posts, newest first
const page = await db.orm.posts
  .orderBy({ createdAt: -1 })
  .limit(20)
  .offset(20)
  .all();

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

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

On PostgreSQL, a sort can also use a related record, a count, and a placement for nulls:

TypeScript
// Posts by their author's name
const byAuthor = await db.orm.public.Post
  .orderBy([(p) => p.author.name.asc(), (p) => p.id.asc()])
  .all();

// Users by how many posts they have
const mostPosts = await db.orm.public.User
  .orderBy((u) => u.posts.count().desc())
  .all();

// Posts without content go last
const titled = await db.orm.public.Post
  .orderBy((p) => p.content.desc({ nulls: "last" }))
  .all();

A relation to one record gives you that record's fields, one relation deep. A relation to many records gives you count(), which takes an optional filter, as in u.posts.count((p) => p.published.eq(true)). The orderBy() reference has the details.

For a composite sort on PostgreSQL, pass an array of callbacks, and 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();

On PostgreSQL, a sort can also use a related record, a count, and a placement for nulls:

// Posts by their author's name

const byAuthor = await db.orm.public.Post

  .orderBy([(p) => p.author.name.asc(), (p) => p.id.asc()])

  .all();

// Users by how many posts they have

const mostPosts = await db.orm.public.User

  .orderBy((u) => u.posts.count().desc())

  .all();

// Posts without content go last

const titled = await db.orm.public.Post

  .orderBy((p) => p.content.desc({ nulls: "last" }))

  .all();

A relation to one record gives you that record's fields, one relation deep. A relation to many records gives you count(), which takes an optional filter, as in u.posts.count((p) => p.published.eq(true)). The orderBy() reference has the details.

Offset gets slower on deep pages, so 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, so 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, because 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();

A cursor works only when every sort is a plain field of the model. With a sort by a related record, a count, an extension operation such as a vector distance, or a nulls placement, .cursor(...) throws an error whose code is ORM.ARGUMENT_INVALID, so page those queries with .limit(n) and .offset(n).

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

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 }

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, which 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.

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.

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();

.all() returns every match, so 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, and when you do need every record, page through the table: .cursor(...) on PostgreSQL, .offset(n) on MongoDB.

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

Projects created with npm create prisma@latest include the Prisma ORM skills for your coding agent, and in an existing project you run npx prisma skills sync to add them. Skills are instruction files a coding agent reads, and the prisma-8 skill covers everything on this page, so try 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()."
Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu