Learn how Apex Cursors let you fetch and render large Salesforce datasets in manageable chunks for smoother LWC experiences. This post includes an Apex cursor wrapper and a sample LWC to iterate through cursor chunks.
Why Apex Cursors?
Apex Cursors (Beta) allow you to query very large result sets without loading all records into memory at once. Instead, you can fetch results in small batches and navigate forward (and backward) through the dataset. This reduces heap usage, avoids governor-limit related issues, and provides a more responsive UI when building Lightning Web Components that surface many records.
How this works
The pattern below uses a lightweight wrapper that serializes the cursor locator and maintains a current position and batch size. From LWC, you pass the wrapper to an @AuraEnabled Apex method that returns the next batch of records and an updated serialized cursor locator.
Example Apex controller (CursorController.cls)
public with sharing class CursorController {
@AuraEnabled
public static CursorResultWrapper processCursor(CursorResultWrapper wrapper){
try {
Database.Cursor cursorLocator;
if(wrapper.stringlocator == null){
cursorLocator = Database.getCursor('SELECT Id, Name FROM Contact');
}else{
cursorLocator = (Database.Cursor)JSON.deserializeStrict(wrapper.stringlocator, Database.Cursor.class);
}
wrapper.records = cursorLocator.fetch(wrapper.position, wrapper.batchSize);
wrapper.stringlocator = JSON.serialize(cursorLocator);
wrapper.position++;
return wrapper;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
public class CursorResultWrapper{
@auraEnabled
public Integer position{get;set;}
@auraEnabled
public Integer batchSize{get;set;}
@auraEnabled
public List records{get;set;}
@auraEnabled
public string stringlocator{get;set;}
}
}
Sample LWC markup (cursorLWC.html)
<template>
<lightning-card title="Call Next Chunk" icon-name="utility:connected_apps">
<div class="slds-m-around_medium">
<lightning-button label="Next Result set" onclick={callNextChunkFromCursor}></lightning-button>
<p class="slds-m-top_medium">current cursor position : {cursorWrapper.position}</p>
<p class="slds-m-top_medium">cursor chunk size: {cursorWrapper.batchSize}</p>
<p class="slds-m-top_medium">cursor results</p>
<template lwc:if={cursorWrapper.records} for:each={cursorWrapper.records} for:item="contact">
<p key={contact.Id}>{contact.Name}</p>
</template>
</div>
</lightning-card>
</template>
Sample LWC JavaScript (cursorLWC.js)
import { LightningElement, track } from 'lwc';
import processCursor from '@salesforce/apex/CursorController.processCursor';
export default class ProcessLargeDataset extends LightningElement {
@track cursorWrapper = {
position : 0,
batchSize : 2,
records : null,
stringlocator: null
}
connectedCallback(){
this.callNextChunkFromCursor(true);
}
async callNextChunkFromCursor(isloading) {
try {
let result = await processCursor({ wrapper: this.cursorWrapper });
this.cursorWrapper = JSON.parse(JSON.stringify(result));
} catch (error) {
console.log(error);
}
}
}
Key highlights & best practices
- Use small batch sizes (adjust to your UI and performance requirements).
- Serialize the cursor locator and send it back from Apex so the client can continue fetching the next chunk.
- Prefer server-side filtering and selective fields in SELECT to reduce transferred data.
- Gracefully handle end-of-data conditions and errors (e.g., when fetch returns empty list).
- Test cursor behavior in your org since the feature may be in Beta and limits/behavior can change.
Use cases
This pattern is useful for:
- Home page or console components that progressively load contact or account lists.
- Infinite-scroll UIs and paginated views in LWC.
- Dashboards that preview subsets of very large datasets without loading everything.
Reference
Salesforce Apex Cursors official docs: Apex Cursors
Conclusion — Why it matters
For Salesforce admins, developers, and product owners, adopting cursor-based data retrieval helps build scalable, responsive interfaces that avoid common heap and governor limit issues. It’s particularly valuable when your UI needs to present large lists while keeping memory usage predictable. Implement this pattern in LWCs that need to fetch many records and tune batch size and queries for your specific users and org constraints.








Leave a Reply