With query cursors in Cloud Firestore, you can split data returned by a
query into batches according to the parameters you define in your query.
Query cursors define the start and end points for a query, allowing you to:
- Return a subset of the data.
- Paginate query results.
However, to define a specific range for a query, you should use the
where()
method described in
Simple Queries
.
Add a simple cursor to a query
Use the
startAt()
or
startAfter()
methods to define the start point for a query.
The
startAt()
method includes the start point, while the
startAfter()
method
excludes it.
For example, if you use
startAt(A)
in a query, it returns the entire alphabet.
If you use
startAfter(A)
instead, it returns
B-Z
.
Web modular API
import { query, orderBy, startAt } from "firebase/firestore";
const q = query(citiesRef, orderBy("population"), startAt(1000000));
Web namespaced API
citiesRef.orderBy("population").startAt(1000000);
Swift
Note:
This product is not available on watchOS and App Clip targets.
// Get all cities with population over one million, ordered by population.
db.collection("cities")
.order(by: "population")
.start(at: [1000000])
Objective-C
Note:
This product is not available on watchOS and App Clip targets.
// Get all cities with population over one million, ordered by population.
[[[db collectionWithPath:@"cities"]
queryOrderedByField:@"population"]
queryStartingAtValues:@[ @1000000 ]];
Kotlin+KTX
// Get all cities with a population >= 1,000,000, ordered by population,
db.collection("cities")
.orderBy("population")
.startAt(1000000)
Java
// Get all cities with a population >= 1,000,000, ordered by population,
db.collection("cities")
.orderBy("population")
.startAt(1000000);
Dart
db.collection("cities").orderBy("population").startAt([1000000]);
C++
// Get all cities with a population >= 1,000,000, ordered by population,
db->Collection("cities")
.OrderBy("population")
.StartAt({FieldValue::Integer(1000000)});
Unity
Query query = citiesRef.OrderBy("Population").StartAt(1000000);
Similarly, use the
endAt()
or
endBefore()
methods to define an end point
for your query results.
Web modular API
import { query, orderBy, endAt } from "firebase/firestore";
const q = query(citiesRef, orderBy("population"), endAt(1000000));
Web namespaced API
citiesRef.orderBy("population").endAt(1000000);
Swift
Note:
This product is not available on watchOS and App Clip targets.
// Get all cities with population less than one million, ordered by population.
db.collection("cities")
.order(by: "population")
.end(at: [1000000])
Objective-C
Note:
This product is not available on watchOS and App Clip targets.
// Get all cities with population less than one million, ordered by population.
[[[db collectionWithPath:@"cities"]
queryOrderedByField:@"population"]
queryEndingAtValues:@[ @1000000 ]];
Kotlin+KTX
// Get all cities with a population <= 1,000,000, ordered by population,
db.collection("cities")
.orderBy("population")
.endAt(1000000)
Java
// Get all cities with a population <= 1,000,000, ordered by population,
db.collection("cities")
.orderBy("population")
.endAt(1000000);
Dart
db.collection("cities").orderBy("population").endAt([1000000]);
C++
// Get all cities with a population <= 1,000,000, ordered by population,
db->Collection("cities")
.OrderBy("population")
.EndAt({FieldValue::Integer(1000000)});
Unity
Query query = citiesRef.OrderBy("Population").EndAt(1000000);
Use a document snapshot to define the query cursor
You can also pass a document snapshot to the cursor clause as the start or end
point of the query cursor. The values in the document snapshot serve as the
values in the query cursor.
For example, take a snapshot of a "San Francisco" document in your data
set of cities and populations. Then, use that document snapshot as the start
point for your population query cursor. Your query will return all the cities
with a population larger than or equal to San Francisco's, as defined in the
document snapshot.
Web modular API
import { collection, doc, getDoc, query, orderBy, startAt } from "firebase/firestore";
const citiesRef = collection(db, "cities");
const docSnap = await getDoc(doc(citiesRef, "SF"));
// Get all cities with a population bigger than San Francisco
const biggerThanSf = query(citiesRef, orderBy("population"), startAt(docSnap));
// ...
Web namespaced API
var citiesRef = db.collection("cities");
return citiesRef.doc("SF").get().then((doc) => {
// Get all cities with a population bigger than San Francisco
var biggerThanSf = citiesRef
.orderBy("population")
.startAt(doc);
// ...
});
Swift
Note:
This product is not available on watchOS and App Clip targets.
db.collection("cities")
.document("SF")
.addSnapshotListener { (document, error) in
guard let document = document else {
print("Error retreving cities: \(error.debugDescription)")
return
}
// Get all cities with a population greater than or equal to San Francisco.
let sfSizeOrBigger = db.collection("cities")
.order(by: "population")
.start(atDocument: document)
}
Objective-C
Note:
This product is not available on watchOS and App Clip targets.
[[[db collectionWithPath:@"cities"] documentWithPath:@"SF"]
addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *error) {
if (snapshot == nil) {
NSLog(@"Error retreiving cities: %@", error);
return;
}
// Get all cities with a population greater than or equal to San Francisco.
FIRQuery *sfSizeOrBigger = [[[db collectionWithPath:@"cities"]
queryOrderedByField:@"population"]
queryStartingAtDocument:snapshot];
}];
Kotlin+KTX
// Get the data for "San Francisco"
db.collection("cities").document("SF")
.get()
.addOnSuccessListener { documentSnapshot ->
// Get all cities with a population bigger than San Francisco.
val biggerThanSf = db.collection("cities")
.orderBy("population")
.startAt(documentSnapshot)
// ...
}
Java
// Get the data for "San Francisco"
db.collection("cities").document("SF")
.get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
@Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
// Get all cities with a population bigger than San Francisco.
Query biggerThanSf = db.collection("cities")
.orderBy("population")
.startAt(documentSnapshot);
// ...
}
});
Dart
db.collection("cities").doc("SF").get().then(
(documentSnapshot) {
final biggerThanSf = db
.collection("cities")
.orderBy("population")
.startAtDocument(documentSnapshot);
},
onError: (e) => print("Error: $e"),
);
C++
db->Collection("cities").Document("SF").Get().OnCompletion(
[db](const Future<DocumentSnapshot>& future) {
if (future.error() == Error::kErrorOk) {
const DocumentSnapshot& document_snapshot = *future.result();
Query bigger_than_sf = db->Collection("cities")
.OrderBy("population")
.StartAt({document_snapshot});
// ...
}
});
Unity
CollectionReference citiesRef = db.Collection("cities");
DocumentReference docRef = citiesRef.Document("SF");
docRef.GetSnapshotAsync().ContinueWith((snapshotTask) =>
{
Query query = citiesRef.OrderBy("Population").StartAt(snapshotTask.Result);
});
Paginate a query
Paginate queries by combining query cursors with the
limit()
method.
For example, use the last document in a batch as the start of a cursor for the
next batch.
Web modular API
import { collection, query, orderBy, startAfter, limit, getDocs } from "firebase/firestore";
// Query the first page of docs
const first = query(collection(db, "cities"), orderBy("population"), limit(25));
const documentSnapshots = await getDocs(first);
// Get the last visible document
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
const next = query(collection(db, "cities"),
orderBy("population"),
startAfter(lastVisible),
limit(25));
Web namespaced API
var first = db.collection("cities")
.orderBy("population")
.limit(25);
return first.get().then((documentSnapshots) => {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
});
Swift
Note:
This product is not available on watchOS and App Clip targets.
// Construct query for first 25 cities, ordered by population
let first = db.collection("cities")
.order(by: "population")
.limit(to: 25)
first.addSnapshotListener { (snapshot, error) in
guard let snapshot = snapshot else {
print("Error retreving cities: \(error.debugDescription)")
return
}
guard let lastSnapshot = snapshot.documents.last else {
// The collection is empty.
return
}
// Construct a new query starting after this document,
// retrieving the next 25 cities.
let next = db.collection("cities")
.order(by: "population")
.start(afterDocument: lastSnapshot)
// Use the query for pagination.
// ...
}
Objective-C
Note:
This product is not available on watchOS and App Clip targets.
FIRQuery *first = [[[db collectionWithPath:@"cities"]
queryOrderedByField:@"population"]
queryLimitedTo:25];
[first addSnapshotListener:^(FIRQuerySnapshot *snapshot, NSError *error) {
if (snapshot == nil) {
NSLog(@"Error retreiving cities: %@", error);
return;
}
if (snapshot.documents.count == 0) { return; }
FIRDocumentSnapshot *lastSnapshot = snapshot.documents.lastObject;
// Construct a new query starting after this document,
// retreiving the next 25 cities.
FIRQuery *next = [[[db collectionWithPath:@"cities"]
queryOrderedByField:@"population"]
queryStartingAfterDocument:lastSnapshot];
// Use the query for pagination.
// ...
}];
Kotlin+KTX
// Construct query for first 25 cities, ordered by population
val first = db.collection("cities")
.orderBy("population")
.limit(25)
first.get()
.addOnSuccessListener { documentSnapshots ->
// ...
// Get the last visible document
val lastVisible = documentSnapshots.documents[documentSnapshots.size() - 1]
// Construct a new query starting at this document,
// get the next 25 cities.
val next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25)
// Use the query for pagination
// ...
}
Java
// Construct query for first 25 cities, ordered by population
Query first = db.collection("cities")
.orderBy("population")
.limit(25);
first.get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
@Override
public void onSuccess(QuerySnapshot documentSnapshots) {
// ...
// Get the last visible document
DocumentSnapshot lastVisible = documentSnapshots.getDocuments()
.get(documentSnapshots.size() -1);
// Construct a new query starting at this document,
// get the next 25 cities.
Query next = db.collection("cities")
.orderBy("population")
.startAfter(lastVisible)
.limit(25);
// Use the query for pagination
// ...
}
});
Dart
// Construct query for first 25 cities, ordered by population
final first = db.collection("cities").orderBy("population").limit(25);
first.get().then(
(documentSnapshots) {
// Get the last visible document
final lastVisible = documentSnapshots.docs[documentSnapshots.size - 1];
// Construct a new query starting at this document,
// get the next 25 cities.
final next = db
.collection("cities")
.orderBy("population")
.startAfterDocument(lastVisible).limit(25);
// Use the query for pagination
// ...
},
onError: (e) => print("Error completing: $e"),
);
C++
// Construct query for first 25 cities, ordered by population
Query first = db->Collection("cities").OrderBy("population").Limit(25);
first.Get().OnCompletion([db](const Future<QuerySnapshot>& future) {
if (future.error() != Error::kErrorOk) {
// Handle error...
return;
}
// Get the last visible document
const QuerySnapshot& document_snapshots = *future.result();
std::vector<DocumentSnapshot> documents = document_snapshots.documents();
const DocumentSnapshot& last_visible = documents.back();
// Construct a new query starting at this document,
// get the next 25 cities.
Query next = db->Collection("cities")
.OrderBy("population")
.StartAfter(last_visible)
.Limit(25);
// Use the query for pagination
// ...
});
Unity
CollectionReference citiesRef = db.Collection("cities");
Query firstQuery = citiesRef.OrderBy("Population").Limit(3);
// Get the last document from the results
firstQuery.GetSnapshotAsync().ContinueWith((querySnapshotTask) =>
{
long lastPopulation = 0;
foreach (DocumentSnapshot documentSnapshot in querySnapshotTask.Result.Documents)
{
lastPopulation = documentSnapshot.GetValue<long>("Population");
}
// Construct a new query starting at this document.
// Note: this will not have the desired effect if multiple cities have the exact same population value
Query secondQuery = citiesRef.OrderBy("Population").StartAfter(lastPopulation);
Task<QuerySnapshot> secondQuerySnapshot = secondQuery.GetSnapshotAsync();
Set cursor based on multiple fields
When using a cursor based on a field value (not a DocumentSnapshot), you can
make the cursor position more precise by adding additional fields. This is
particularly useful if your data set includes multiple documents that all have
the same value for your cursor field, making the cursor's position ambiguous.
You can add additional field values to your cursor to further specify the start
or end point and reduce ambiguity.
For example, in a data set containing all the cities named "Springfield" in the
United States, there would be multiple start points for a query set to
start at "Springfield":
Cities
|
Name
|
State
|
Springfield
|
Massachusetts
|
Springfield
|
Missouri
|
Springfield
|
Wisconsin
|
To start at a specific Springfield, you could add the state as a secondary
condition in your cursor clause.
Web modular API
// Will return all Springfields
import { collection, query, orderBy, startAt } from "firebase/firestore";
const q1 = query(collection(db, "cities"),
orderBy("name"),
orderBy("state"),
startAt("Springfield"));
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
const q2 = query(collection(db, "cities"),
orderBy("name"),
orderBy("state"),
startAt("Springfield", "Missouri"));
Web namespaced API
// Will return all Springfields
db.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt("Springfield");
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
db.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt("Springfield", "Missouri");
Swift
Note:
This product is not available on watchOS and App Clip targets.
// Will return all Springfields
db.collection("cities")
.order(by: "name")
.order(by: "state")
.start(at: ["Springfield"])
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
db.collection("cities")
.order(by: "name")
.order(by: "state")
.start(at: ["Springfield", "Missouri"])
Objective-C
Note:
This product is not available on watchOS and App Clip targets.
// Will return all Springfields
[[[[db collectionWithPath:@"cities"]
queryOrderedByField:@"name"]
queryOrderedByField:@"state"]
queryStartingAtValues:@[ @"Springfield" ]];
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
[[[[db collectionWithPath:@"cities"]
queryOrderedByField:@"name"]
queryOrderedByField:@"state"]
queryStartingAtValues:@[ @"Springfield", @"Missouri" ]];
Kotlin+KTX
// Will return all Springfields
db.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt("Springfield")
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
db.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt("Springfield", "Missouri")
Java
// Will return all Springfields
db.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt("Springfield");
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
db.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt("Springfield", "Missouri");
Dart
// Will return all Springfields
db
.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt(["Springfield"]);
// Will return "Springfield, Missouri" and "Springfield, Wisconsin"
db
.collection("cities")
.orderBy("name")
.orderBy("state")
.startAt(["Springfield", "Missouri"]);
C++
// This is not yet supported.
Unity
Query query1 = db.Collection("cities").OrderBy("Name").OrderBy("State").StartAt("Springfield");
Query query2 = db.Collection("cities").OrderBy("Name").OrderBy("State").StartAt("Springfield", "Missouri");