← Back to list

Apex Cursor-Based Pagination in Salesforce: A Production-Ready Guide with Next and Previous Page…

Most Salesforce developers learn pagination using OFFSET.

HV · 2026-06-01 02:45 · 0 claps · 2.6 min read
#salesforce-apex #pagination #salesforce-lwc #salesforce-performance #salesforce-development
Open on Medium ↗
Wiki topics: CRM · Email & CRM

Apex Cursor-Based Pagination in Salesforce: A Production-Ready Guide with Next and Previous Page Support

Most Salesforce developers learn pagination using OFFSET.

Example:

SELECT Id, Name
FROM Account
ORDER BY CreatedDate
LIMIT 50 OFFSET 2000

This query returns records starting after the first 2,000 rows.

To return records 2001–2050, Salesforce must first locate and skip the first 2,000 records.

This creates two significant limitations:

  1. Performance Degrades as Offset Increases

Each page request requires Salesforce to process all skipped rows before returning the requested records. Larger offsets generally result in more database work.

  1. OFFSET Has a Hard Limit

Salesforce enforces a maximum OFFSET value of 2,000.Attempting to exceed this limit results in an exception.

Example:

SELECT Id, Name
FROM Account
ORDER BY CreatedDate
LIMIT 50 OFFSET 2001  

will result in below exception:

NUMBER_OUTSIDE_VALID_RANGE: Maximum SOQL offset allowed is 2000

For applications that need to navigate beyond the first few thousand records, OFFSET is no longer a viable long-term solution.

This is where cursor-based pagination becomes the preferred architecture.

What is Cursor-Based Pagination?

Instead of requesting:

Give me Page 20

you request:

Give me the next 50 records after this position.

That position is called a cursor.

Common cursor candidates include:

  • CreatedDate
  • LastModifiedDate
  • Auto Number
  • External Id
  • Indexed Custom Fields

For Salesforce, the most reliable cursor pattern is:

CreatedDate + Id

This combination creates a unique and stable position for every record.

Understanding the Cursor

Suppose we have the following records:

Notice that multiple records share the exact same timestamp.

This commonly occurs during:

  • Bulk API operations
  • Data Loader imports
  • Batch Apex executions
  • ETL processes
  • Enterprise integrations

If we sort only by CreatedDate, the ordering is not guaranteed.

Instead *ORDER BY CreatedDate, Id* creates a deterministic ordering.

Why CreatedDate Alone Can Fail

Suppose Page 1 ends with:

Id = 001A CreatedDate = 2026–05–30T10:15:23.847Z

Your next-page query is:

SELECT Id, Name, CreatedDate
FROM Account
WHERE CreatedDate > :cursorDate
ORDER BY CreatedDate
LIMIT 50

The following records would be skipped: (refer the records screenshot above)

  • 001B
  • 001C

because their CreatedDate equals the cursor value rather than being greater than it. This results in missing records.

The Correct Cursor Pattern

To load the Next Page use below code

SELECT Id, Name, CreatedDate
FROM Account
WHERE (
    CreatedDate > :cursorDate
    OR (
        CreatedDate = :cursorDate
        AND Id > :cursorId
    )
)
ORDER BY CreatedDate ASC, Id ASC
LIMIT :pageSize    

Salesforce can jump directly to the next position without scanning thousands of skipped rows.

To load the Previous Page, use the below code

SELECT Id, Name, CreatedDate
FROM Account
WHERE (
    CreatedDate < :cursorDate
    OR (
        CreatedDate = :cursorDate
        AND Id < :cursorId
    )
)
ORDER BY CreatedDate DESC, Id DESC
LIMIT :pageSize

Previous-page navigation requires reversing the comparison and sort order.

Since the results arrive in reverse order, they must be reversed again in Apex before being returned to the client.

Performance Comparison

Final Thoughts

Cursor-based pagination is one of the most valuable Salesforce performance patterns available to developers building enterprise applications.

The key insight is not simply replacing OFFSET.

The real goal is to create a stable, deterministic cursor using:

CreatedDate + Id

CreatedDate already provides millisecond precision.

The additional Id ensures that records sharing the same timestamp are never skipped or duplicated.

Together, these fields create a scalable cursor capable of navigating millions of records while avoiding the performance limitations and hard boundaries imposed by OFFSET-based pagination.

For enterprise Salesforce applications, APIs, integrations, and Lightning Web Components, cursor-based pagination should be the default approach rather than an optimization added later.


메타데이터
post_id
72cd3f4b0091
slug
apex-cursor-based-pagination-in-salesforce-a-production-ready-guide-with-next-and-previous-page-72cd3f4b0091
url
https://medium.com/@harshvikram16/apex-cursor-based-pagination-in-salesforce-a-production-ready-guide-with-next-and-previous-page-72cd3f4b0091
canonical_url
https://medium.com/@harshvikram16/apex-cursor-based-pagination-in-salesforce-a-production-ready-guide-with-next-and-previous-page-72cd3f4b0091
author_url
https://medium.com/@harshvikram16
status
ok
fetched_at
2026-07-14 07:37:53