Stay organized with collectionsSave and categorize content based on your preferences.
With query cursors inCloud 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 thewhere()method described inSimple Queries.
Add a simple cursor to a query
Use thestartAt()orstartAfter()methods to define the start point for a query.
ThestartAt()method includes the start point, while thestartAfter()method
excludes it.
For example, if you usestartAt(A)in a query, it returns the entire alphabet.
If you usestartAfter(A)instead, it returnsB-Z.
Note:This product is not available on watchOS and App Clip targets.
// Get all cities with population over one million, ordered by population.[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"population"]queryStartingAtValues:@[@1000000]];
// Get all cities with a population >= 1,000,000, ordered by population,db->Collection("cities").OrderBy("population").StartAt({FieldValue::Integer(1000000)});
Note:This product is not available on watchOS and App Clip targets.
// Get all cities with population less than one million, ordered by population.[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"population"]queryEndingAtValues:@[@1000000]];
// Get all cities with a population <= 1,000,000, ordered by population,db->Collection("cities").OrderBy("population").EndAt({FieldValue::Integer(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
import{collection,doc,getDoc,query,orderBy,startAt}from"firebase/firestore";constcitiesRef=collection(db,"cities");constdocSnap=awaitgetDoc(doc(citiesRef,"SF"));// Get all cities with a population bigger than San FranciscoconstbiggerThanSf=query(citiesRef,orderBy("population"),startAt(docSnap));// ...
varcitiesRef=db.collection("cities");returncitiesRef.doc("SF").get().then((doc)=>{// Get all cities with a population bigger than San FranciscovarbiggerThanSf=citiesRef.orderBy("population").startAt(doc);// ...});
Note:This product is not available on watchOS and App Clip targets.
db.collection("cities").document("SF").addSnapshotListener{(document,error)inguardletdocument=documentelse{print("Error retreving cities:\(error.debugDescription)")return}// Get all cities with a population greater than or equal to San Francisco.letsfSizeOrBigger=db.collection("cities").order(by:"population").start(atDocument:document)}
Note:This product is not available on watchOS and App Clip targets.
[[[dbcollectionWithPath:@"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=[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"population"]queryStartingAtDocument:snapshot];}];
// Get the data for "San Francisco"db.collection("cities").document("SF").get().addOnSuccessListener{documentSnapshot->// Get all cities with a population bigger than San Francisco.valbiggerThanSf=db.collection("cities").orderBy("population").startAt(documentSnapshot)// ...}
// Get the data for "San Francisco"db.collection("cities").document("SF").get().addOnSuccessListener(newOnSuccessListener<DocumentSnapshot>(){@OverridepublicvoidonSuccess(DocumentSnapshotdocumentSnapshot){// Get all cities with a population bigger than San Francisco.QuerybiggerThanSf=db.collection("cities").orderBy("population").startAt(documentSnapshot);// ...}});
// Fetch the snapshot with an API call, waiting for a maximum of 30 seconds for a result.ApiFuture<DocumentSnapshot>future=db.collection("cities").document("SF").get();DocumentSnapshotsnapshot=future.get(30,TimeUnit.SECONDS);// Construct the queryQueryquery=db.collection("cities").orderBy("population").startAt(snapshot);
Paginate queries by combining query cursors with thelimit()method.
For example, use the last document in a batch as the start of a cursor for the
next batch.
Web
import{collection,query,orderBy,startAfter,limit,getDocs}from"firebase/firestore";// Query the first page of docsconstfirst=query(collection(db,"cities"),orderBy("population"),limit(25));constdocumentSnapshots=awaitgetDocs(first);// Get the last visible documentconstlastVisible=documentSnapshots.docs[documentSnapshots.docs.length-1];console.log("last",lastVisible);// Construct a new query starting at this document,// get the next 25 cities.constnext=query(collection(db,"cities"),orderBy("population"),startAfter(lastVisible),limit(25));
varfirst=db.collection("cities").orderBy("population").limit(25);returnfirst.get().then((documentSnapshots)=>{// Get the last visible documentvarlastVisible=documentSnapshots.docs[documentSnapshots.docs.length-1];console.log("last",lastVisible);// Construct a new query starting at this document,// get the next 25 cities.varnext=db.collection("cities").orderBy("population").startAfter(lastVisible).limit(25);});
Note:This product is not available on watchOS and App Clip targets.
// Construct query for first 25 cities, ordered by populationletfirst=db.collection("cities").order(by:"population").limit(to:25)first.addSnapshotListener{(snapshot,error)inguardletsnapshot=snapshotelse{print("Error retreving cities:\(error.debugDescription)")return}guardletlastSnapshot=snapshot.documents.lastelse{// The collection is empty.return}// Construct a new query starting after this document,// retrieving the next 25 cities.letnext=db.collection("cities").order(by:"population").start(afterDocument:lastSnapshot)// Use the query for pagination.// ...}
Note:This product is not available on watchOS and App Clip targets.
FIRQuery*first=[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"population"]queryLimitedTo:25];[firstaddSnapshotListener:^(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=[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"population"]queryStartingAfterDocument:lastSnapshot];// Use the query for pagination.// ...}];
// Construct query for first 25 cities, ordered by populationvalfirst=db.collection("cities").orderBy("population").limit(25)first.get().addOnSuccessListener{documentSnapshots->// ...// Get the last visible documentvallastVisible=documentSnapshots.documents[documentSnapshots.size()-1]// Construct a new query starting at this document,// get the next 25 cities.valnext=db.collection("cities").orderBy("population").startAfter(lastVisible).limit(25)// Use the query for pagination// ...}
// Construct query for first 25 cities, ordered by populationQueryfirst=db.collection("cities").orderBy("population").limit(25);first.get().addOnSuccessListener(newOnSuccessListener<QuerySnapshot>(){@OverridepublicvoidonSuccess(QuerySnapshotdocumentSnapshots){// ...// Get the last visible documentDocumentSnapshotlastVisible=documentSnapshots.getDocuments().get(documentSnapshots.size()-1);// Construct a new query starting at this document,// get the next 25 cities.Querynext=db.collection("cities").orderBy("population").startAfter(lastVisible).limit(25);// Use the query for pagination// ...}});
// Construct query for first 25 cities, ordered by populationfinalfirst=db.collection("cities").orderBy("population").limit(25);first.get().then((documentSnapshots){// Get the last visible documentfinallastVisible=documentSnapshots.docs[documentSnapshots.size-1];// Construct a new query starting at this document,// get the next 25 cities.finalnext=db.collection("cities").orderBy("population").startAfterDocument(lastVisible).limit(25);// Use the query for pagination// ...},onError:(e)=>print("Error completing:$e"),);
// Construct query for first 25 cities, ordered by population.CollectionReferencecities=db.collection("cities");QueryfirstPage=cities.orderBy("population").limit(25);// Wait for the results of the API call, waiting for a maximum of 30 seconds for a result.ApiFuture<QuerySnapshot>future=firstPage.get();List<QueryDocumentSnapshot>docs=future.get(30,TimeUnit.SECONDS).getDocuments();// Construct query for the next 25 cities.QueryDocumentSnapshotlastDoc=docs.get(docs.size()-1);QuerysecondPage=cities.orderBy("population").startAfter(lastDoc).limit(25);future=secondPage.get();docs=future.get(30,TimeUnit.SECONDS).getDocuments();
cities_ref=db.collection("cities")first_query=cities_ref.order_by("population").limit(3)# Get the last document from the resultsdocs=first_query.stream()last_doc=list(docs)[-1]# Construct a new query starting at this document# Note: this will not have the desired effect if# multiple cities have the exact same population valuelast_pop=last_doc.to_dict()["population"]next_query=(cities_ref.order_by("population").start_after({"population":last_pop}).limit(3))# Use the query for pagination# ...
cities_ref=db.collection("cities")first_query=cities_ref.order_by("population").limit(3)# Get the last document from the resultsdocs=[dasyncfordinfirst_query.stream()]last_doc=list(docs)[-1]# Construct a new query starting at this document# Note: this will not have the desired effect if# multiple cities have the exact same population valuelast_pop=last_doc.to_dict()["population"]next_query=(cities_ref.order_by("population").start_after({"population":last_pop}).limit(3))# Use the query for pagination# ...
// Construct query for first 25 cities, ordered by populationQueryfirst=db->Collection("cities").OrderBy("population").Limit(25);first.Get().OnCompletion([db](constFuture<QuerySnapshot>&future){if(future.error()!=Error::kErrorOk){// Handle error...return;}// Get the last visible documentconstQuerySnapshot&document_snapshots=*future.result();std::vector<DocumentSnapshot>documents=document_snapshots.documents();constDocumentSnapshot&last_visible=documents.back();// Construct a new query starting at this document,// get the next 25 cities.Querynext=db->Collection("cities").OrderBy("population").StartAfter(last_visible).Limit(25);// Use the query for pagination// ...});
constfirst=db.collection('cities').orderBy('population').limit(3);constsnapshot=awaitfirst.get();// Get the last documentconstlast=snapshot.docs[snapshot.docs.length-1];// 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.constnext=db.collection('cities').orderBy('population').startAfter(last.data().population).limit(3);// Use the query for pagination// ...
cities:=client.Collection("cities")// Get the first 25 cities, ordered by population.firstPage:=cities.OrderBy("population",firestore.Asc).Limit(25).Documents(ctx)docs,err:=firstPage.GetAll()iferr!=nil{returnerr}// Get the last document.lastDoc:=docs[len(docs)-1]// Construct a new query to get the next 25 cities.secondPage:=cities.OrderBy("population",firestore.Asc).StartAfter(lastDoc.Data()["population"]).Limit(25)// ...
$citiesRef = $db->collection('samples/php/cities');$firstQuery = $citiesRef->orderBy('population')->limit(3);# Get the last document from the results$documents = $firstQuery->documents();$lastPopulation = 0;foreach ($documents as $document) {$lastPopulation = $document['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$nextQuery = $citiesRef->orderBy('population')->startAfter([$lastPopulation]);$snapshot = $nextQuery->documents();
CollectionReferencecitiesRef=db.Collection("cities");QueryfirstQuery=citiesRef.OrderBy("Population").Limit(3);// Get the last document from the resultsfirstQuery.GetSnapshotAsync().ContinueWith((querySnapshotTask)=>{longlastPopulation=0;foreach(DocumentSnapshotdocumentSnapshotinquerySnapshotTask.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 valueQuerysecondQuery=citiesRef.OrderBy("Population").StartAfter(lastPopulation);Task<QuerySnapshot>secondQuerySnapshot=secondQuery.GetSnapshotAsync();
C#
CollectionReferencecitiesRef=db.Collection("cities");QueryfirstQuery=citiesRef.OrderBy("Population").Limit(3);// Get the last document from the resultsQuerySnapshotquerySnapshot=awaitfirstQuery.GetSnapshotAsync();longlastPopulation=0;foreach(DocumentSnapshotdocumentSnapshotinquerySnapshot.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 valueQuerysecondQuery=citiesRef.OrderBy("Population").StartAfter(lastPopulation);QuerySnapshotsecondQuerySnapshot=awaitsecondQuery.GetSnapshotAsync();
cities_ref=firestore.colcollection_pathfirst_query=cities_ref.order("population").limit(3)# Get the last document from the results.last_population=0first_query.getdo|city|last_population=city.data[:population]end# 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.second_query=cities_ref.order("population").start_after(last_population)second_query.getdo|city|puts"Document#{city.document_id}returned by paginated query cursor."end
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
// Will return all Springfieldsimport{collection,query,orderBy,startAt}from"firebase/firestore";constq1=query(collection(db,"cities"),orderBy("name"),orderBy("state"),startAt("Springfield"));// Will return "Springfield, Missouri" and "Springfield, Wisconsin"constq2=query(collection(db,"cities"),orderBy("name"),orderBy("state"),startAt("Springfield","Missouri"));
// Will return all Springfieldsdb.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");
Note:This product is not available on watchOS and App Clip targets.
// Will return all Springfieldsdb.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"])
Note:This product is not available on watchOS and App Clip targets.
// Will return all Springfields[[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"name"]queryOrderedByField:@"state"]queryStartingAtValues:@[@"Springfield"]];// Will return "Springfield, Missouri" and "Springfield, Wisconsin"[[[[dbcollectionWithPath:@"cities"]queryOrderedByField:@"name"]queryOrderedByField:@"state"]queryStartingAtValues:@[@"Springfield",@"Missouri"]];
// Will return all Springfieldsdb.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")
// Will return all Springfieldsdb.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");
// Will return all Springfieldsdb.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"]);
// Will return all SpringfieldsQueryquery1=db.collection("cities").orderBy("name").orderBy("state").startAt("Springfield");// Will return "Springfield, Missouri" and "Springfield, Wisconsin"Queryquery2=db.collection("cities").orderBy("name").orderBy("state").startAt("Springfield","Missouri");
// Will return all SpringfieldsconststartAtNameRes=awaitdb.collection('cities').orderBy('name').orderBy('state').startAt('Springfield').get();// Will return 'Springfield, Missouri' and 'Springfield, Wisconsin'conststartAtNameAndStateRes=awaitdb.collection('cities').orderBy('name').orderBy('state').startAt('Springfield','Missouri').get();
// Will return all Springfields.client.Collection("cities").OrderBy("name",firestore.Asc).OrderBy("state",firestore.Asc).StartAt("Springfield")// Will return Springfields where state comes after Wisconsin.client.Collection("cities").OrderBy("name",firestore.Asc).OrderBy("state",firestore.Asc).StartAt("Springfield","Wisconsin")
// Will return all Springfields$query1 = $db->collection('samples/php/cities')->orderBy('name')->orderBy('state')->startAt(['Springfield']);// Will return "Springfield, Missouri" and "Springfield, Wisconsin"$query2 = $db->collection('samples/php/cities')->orderBy('name')->orderBy('state')->startAt(['Springfield', 'Missouri']);
# Will return all Springfieldsquery1=firestore.col(collection_path).order("name").order("state").start_at("Springfield")# Will return "Springfield, Missouri" and "Springfield, Wisconsin"query2=firestore.col(collection_path).order("name").order("state").start_at(["Springfield","Missouri"])
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-05 UTC."],[],[],null,[]]