When building applications with large datasets, one of the most common challenges is implementing efficient pagination. In Prisma, you can use the findMany method along with take and skip to handle pagination easily. However, when paginating data, you often need the total record count to display the number of pages, and Prisma doesn’t provide this automatically in a single query. But don’t worry! In this guide, I’ll show you how to implement pagination in Prisma and fetch the total record count using a shared where clause within a transaction.
Thank me by sharing on Twitter 🙏
By the end of this post, you’ll know how to efficiently paginate data and ensure your queries are consistent and reliable.
Why Is Pagination Important?
Pagination allows you to manage large datasets by breaking them down into manageable pages. Instead of loading thousands of records at once, which would overwhelm both your server and users, you can load data in smaller chunks. But pagination alone is not enough—often, we also need to know the total number of records to calculate how many pages are needed or display a “showing X of Y results” message to users.
However, Prisma doesn’t give us the total record count directly when using take for pagination. To get around this, we need to run a second query, and in most cases, we want both the paginated data and the count to be consistent, which can be done using a transaction.
Step 1: Set Up Pagination with Prisma
First, let’s start by looking at how to use Prisma’s findMany method to paginate results. When paginating, we typically use the take argument to specify how many records we want per page and skip to determine which page we are on. Here’s an example:
If Anyone Builds It, Everyone Dies: Why Superhuman AI Would Kill Us All
$19.68 (as of January 24, 2026 19:48 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)LISEN USB C to USB C Cable, 5-Pack [3.3/3.3/6.6/6.6/10FT] for iPhone 17 Charger Cord, 60W C to C Cable Fast Charging for iPad, iPhone 17 Air 16 15 Pro Max, Samsung Z Flip 7/S25/S24 USBC to USBC Cable
$8.97 (as of January 21, 2026 03:31 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)Elebase USB to USB C Adapter for iPhone 17 4Pack,USBC Female to A Male Car Charger Adapter,Type C Converter Apple 16 Pro Max 15 14 Plus,iWatch Watch 11 10 Se Ultra 3,iPad Air,Samsung Galaxy S25,Black
$8.99 (as of January 21, 2026 03:31 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)const page = 1;
const pageSize = 10;
const paginatedResults = await prisma.modelName.findMany({
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: {
createdAt: 'desc',
},
});In this example, we fetch 10 records per page. The skip argument allows us to jump over the records of previous pages. If we are on page 1, we skip 0 records ((page - 1) * pageSize = 0). If we’re on page 2, we skip 10 records ((page - 1) * pageSize = 10), and so on.
This is a great start, but it’s incomplete. Users will want to know how many total pages there are, which means we need the total count of records.
Step 2: Get the Total Record Count
To get the total count of records, we can use Prisma’s count method:
const totalCount = await prisma.modelName.count({
where: {
status: 'ACTIVE',
},
});This gives us the total number of active records in our table. However, if you’re applying filters or conditions in the findMany query, you’ll need to ensure that the same conditions are applied to the count query as well. This can lead to duplication of the where clause, which we’ll address next.
Step 3: Use a Shared Where Clause
To avoid duplicating the filtering logic, we can define a shared where clause object. This way, both the findMany and count queries will reference the same filtering conditions, ensuring consistency between the paginated data and the total count.
Here’s how we can define the shared where clause:
const sharedWhereClause = {
status: 'ACTIVE',
createdAt: {
gte: new Date('2023-01-01'),
},
};Now, we can use this shared where clause in both our findMany and count queries:
const paginatedResults = await prisma.modelName.findMany({
where: sharedWhereClause,
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: {
createdAt: 'desc',
},
});
const totalCount = await prisma.modelName.count({
where: sharedWhereClause,
});This approach ensures that the filters are applied consistently in both queries, so the paginated data and total count are accurate.
Step 4: Wrap It in a Transaction for Consistency
While querying paginated data and the total count separately works fine, there’s always a risk that the underlying data could change between the two queries, especially in a highly concurrent environment. To guarantee consistency, we can wrap these two operations in a transaction.
Prisma provides a prisma.$transaction method that allows you to execute multiple queries in a single transaction. Let’s wrap our findMany and count queries in a transaction:
const [paginatedResults, totalCount] = await prisma.$transaction([
prisma.modelName.findMany({
where: sharedWhereClause,
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: {
createdAt: 'desc',
},
}),
prisma.modelName.count({
where: sharedWhereClause,
}),
]);With this approach, both queries will run inside a single transaction, ensuring they are consistent and refer to the same snapshot of the data. This is especially useful when your data is being frequently updated, as it eliminates the risk of discrepancies between the paginated data and the total count.
Step 5: Return Paginated Results with Total Count
Once you have both the paginated results and the total count, you can return them as part of your API response, like so:
return {
paginatedResults,
totalCount,
page,
pageSize,
};This response includes both the paginated data and the total number of records, allowing your frontend to calculate how many pages are available and to display relevant messages to users.
Conclusion
Implementing pagination with Prisma is straightforward using the findMany method with take and skip. However, getting the total record count to calculate the number of pages or display results in a meaningful way requires an additional step. By combining the findMany and count queries with a shared where clause and wrapping them in a transaction, we ensure that both operations are consistent and reliable.
The ability to run both queries inside a transaction eliminates the risk of data discrepancies and ensures a smooth user experience when navigating through large datasets.
With this pattern, you’ll have the tools you need to handle pagination in Prisma efficiently, while ensuring consistent and accurate data across your queries.


