Get realtime updates with Cloud Firestore

You canlisten to a document with theonSnapshot() method. An initialcall using the callback you provide creates a document snapshot immediately withthe current contents of the single document. Then, each timethe contents change, another call updates the document snapshot.

Note:
  • Pipeline operations don't support realtime listeners. To use realtime listeners with Enterprise edition databases, use core operations as described on this page.
  • Realtime listeners are not supported in the PHP client library.

Web

import{doc,onSnapshot}from"firebase/firestore";constunsub=onSnapshot(doc(db,"cities","SF"),(doc)=>{console.log("Current data: ",doc.data());});

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
db.collection("cities").doc("SF").onSnapshot((doc)=>{console.log("Current data: ",doc.data());});
Swift
Note: This product is not available on watchOS and App Clip targets.
db.collection("cities").document("SF").addSnapshotListener{documentSnapshot,erroringuardletdocument=documentSnapshotelse{print("Error fetching document:\(error!)")return}guardletdata=document.data()else{print("Document data was empty.")return}print("Current data:\(data)")}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
[[[self.dbcollectionWithPath:@"cities"]documentWithPath:@"SF"]addSnapshotListener:^(FIRDocumentSnapshot*snapshot,NSError*error){if(snapshot==nil){NSLog(@"Error fetching document: %@",error);return;}NSLog(@"Current data: %@",snapshot.data);}];

Kotlin

valdocRef=db.collection("cities").document("SF")docRef.addSnapshotListener{snapshot,e->if(e!=null){Log.w(TAG,"Listen failed.",e)return@addSnapshotListener}if(snapshot!=null &&snapshot.exists()){Log.d(TAG,"Current data:${snapshot.data}")}else{Log.d(TAG,"Current data: null")}}

Java

finalDocumentReferencedocRef=db.collection("cities").document("SF");docRef.addSnapshotListener(newEventListener<DocumentSnapshot>(){@OverridepublicvoidonEvent(@NullableDocumentSnapshotsnapshot,@NullableFirebaseFirestoreExceptione){if(e!=null){Log.w(TAG,"Listen failed.",e);return;}if(snapshot!=null &&snapshot.exists()){Log.d(TAG,"Current data: "+snapshot.getData());}else{Log.d(TAG,"Current data: null");}}});

Dart

finaldocRef=db.collection("cities").doc("SF");docRef.snapshots().listen((event)=>print("current data:${event.data()}"),onError:(error)=>print("Listen failed:$error"),);

Often, you want your UI to react to changes in the contents of a Firestore document or collection. You can do so with aStreamBuilder widget that consumes the Firestore snapshot stream:

classUserInformationextendsStatefulWidget{@override_UserInformationStatecreateState()=>_UserInformationState();}class_UserInformationStateextendsState<UserInformation>{finalStream<QuerySnapshot>_usersStream=FirebaseFirestore.instance.collection('users').snapshots();@overrideWidgetbuild(BuildContextcontext){returnStreamBuilder<QuerySnapshot>(stream:_usersStream,builder:(BuildContextcontext,AsyncSnapshot<QuerySnapshot>snapshot){if(snapshot.hasError){returnconstText('Something went wrong');}if(snapshot.connectionState==ConnectionState.waiting){returnconstText("Loading");}returnListView(children:snapshot.data!.docs.map((DocumentSnapshotdocument){Map<String,dynamic>data=document.data()!asMap<String,dynamic>;returnListTile(title:Text(data['full_name']),subtitle:Text(data['company']),);}).toList().cast(),);},);}}
Java
DocumentReferencedocRef=db.collection("cities").document("SF");docRef.addSnapshotListener(newEventListener<DocumentSnapshot>(){@OverridepublicvoidonEvent(@NullableDocumentSnapshotsnapshot,@NullableFirestoreExceptione){if(e!=null){System.err.println("Listen failed: "+e);return;}if(snapshot!=null &&snapshot.exists()){System.out.println("Current data: "+snapshot.getData());}else{System.out.print("Current data: null");}}});
Python
# Create an Event for notifying main thread.callback_done=threading.Event()# Create a callback on_snapshot function to capture changesdefon_snapshot(doc_snapshot,changes,read_time):fordocindoc_snapshot:print(f"Received document snapshot:{doc.id}")callback_done.set()doc_ref=db.collection("cities").document("SF")# Watch the documentdoc_watch=doc_ref.on_snapshot(on_snapshot)
C++
DocumentReferencedoc_ref=db->Collection("cities").Document("SF");doc_ref.AddSnapshotListener([](constDocumentSnapshot&snapshot,Errorerror,conststd::string&errorMsg){if(error==Error::kErrorOk){if(snapshot.exists()){std::cout <<"Current data: " <<snapshot <<std::endl;}else{std::cout <<"Current data: null" <<std::endl;}}else{std::cout <<"Listen failed: " <<error <<std::endl;}});
Node.js
constdoc=db.collection('cities').doc('SF');constobserver=doc.onSnapshot(docSnapshot=>{console.log(`Received doc snapshot:${docSnapshot}`);// ...},err=>{console.log(`Encountered error:${err}`);});
Go
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenDocument listens to a single document.funclistenDocument(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"// Сontext with timeout stops listening to changes.ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Doc("SF").Snapshots(ctx)for{snap,err:=it.Next()// DeadlineExceeded will be returned when ctx is cancelled.ifstatus.Code(err)==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}if!snap.Exists(){fmt.Fprintf(w,"Document no longer exists\n")returnnil}fmt.Fprintf(w,"Received document snapshot: %v\n",snap.Data())}}
PHP
// Not supported in the PHP client library
Unity
DocumentReferencedocRef=db.Collection("cities").Document("SF");docRef.Listen(snapshot=>{Debug.Log("Callback received document snapshot.");Debug.Log(String.Format("Document data for {0} document:",snapshot.Id));Dictionary<string,object>city=snapshot.ToDictionary();foreach(KeyValuePair<string,object>pairincity){Debug.Log(String.Format("{0}: {1}",pair.Key,pair.Value));}});
C#
DocumentReferencedocRef=db.Collection("cities").Document("SF");FirestoreChangeListenerlistener=docRef.Listen(snapshot=>{Console.WriteLine("Callback received document snapshot.");Console.WriteLine("Document exists? {0}",snapshot.Exists);if(snapshot.Exists){Console.WriteLine("Document data for {0} document:",snapshot.Id);Dictionary<string,object>city=snapshot.ToDictionary();foreach(KeyValuePair<string,object>pairincity){Console.WriteLine("{0}: {1}",pair.Key,pair.Value);}}});
Ruby
doc_ref=firestore.col(collection_path).docdocument_pathsnapshots=[]# Watch the document.listener=doc_ref.listendo|snapshot|puts"Received document snapshot:#{snapshot.document_id}"snapshots <<snapshotend

Events for local changes

Local writes in your app will invoke snapshot listeners immediately.This is because of an important feature called "latency compensation."When you perform a write, your listeners will be notified with the newdatabefore the data is sent to the backend.

Retrieved documents have ametadata.hasPendingWrites property that indicateswhether the document has local changes that haven't been written to the backendyet. You can use this property to determine the source of events received byyour snapshot listener:

Web

import{doc,onSnapshot}from"firebase/firestore";constunsub=onSnapshot(doc(db,"cities","SF"),(doc)=>{constsource=doc.metadata.hasPendingWrites?"Local":"Server";console.log(source," data: ",doc.data());});

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
db.collection("cities").doc("SF").onSnapshot((doc)=>{varsource=doc.metadata.hasPendingWrites?"Local":"Server";console.log(source," data: ",doc.data());});
Swift
Note: This product is not available on watchOS and App Clip targets.
db.collection("cities").document("SF").addSnapshotListener{documentSnapshot,erroringuardletdocument=documentSnapshotelse{print("Error fetching document:\(error!)")return}letsource=document.metadata.hasPendingWrites?"Local":"Server"print("\(source) data:\(document.data()??[:])")}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
[[[self.dbcollectionWithPath:@"cities"]documentWithPath:@"SF"]addSnapshotListener:^(FIRDocumentSnapshot*snapshot,NSError*error){if(snapshot==nil){NSLog(@"Error fetching document: %@",error);return;}NSString*source=snapshot.metadata.hasPendingWrites?@"Local":@"Server";NSLog(@"%@ data: %@",source,snapshot.data);}];

Kotlin

valdocRef=db.collection("cities").document("SF")docRef.addSnapshotListener{snapshot,e->if(e!=null){Log.w(TAG,"Listen failed.",e)return@addSnapshotListener}valsource=if(snapshot!=null &&snapshot.metadata.hasPendingWrites()){"Local"}else{"Server"}if(snapshot!=null &&snapshot.exists()){Log.d(TAG,"$source data:${snapshot.data}")}else{Log.d(TAG,"$source data: null")}}

Java

finalDocumentReferencedocRef=db.collection("cities").document("SF");docRef.addSnapshotListener(newEventListener<DocumentSnapshot>(){@OverridepublicvoidonEvent(@NullableDocumentSnapshotsnapshot,@NullableFirebaseFirestoreExceptione){if(e!=null){Log.w(TAG,"Listen failed.",e);return;}Stringsource=snapshot!=null &&snapshot.getMetadata().hasPendingWrites()?"Local":"Server";if(snapshot!=null &&snapshot.exists()){Log.d(TAG,source+" data: "+snapshot.getData());}else{Log.d(TAG,source+" data: null");}}});

Dart

finaldocRef=db.collection("cities").doc("SF");docRef.snapshots().listen((event){finalsource=(event.metadata.hasPendingWrites)?"Local":"Server";print("$source data:${event.data()}");},onError:(error)=>print("Listen failed:$error"),);
Java
#NotyetsupportedintheJavaclientlibrary
Python
//NotyetsupportedinPythonclientlibrary
C++
DocumentReferencedoc_ref=db->Collection("cities").Document("SF");doc_ref.AddSnapshotListener([](constDocumentSnapshot&snapshot,Errorerror,conststd::string&errorMsg){if(error==Error::kErrorOk){constchar*source=snapshot.metadata().has_pending_writes()?"Local":"Server";if(snapshot.exists()){std::cout <<source <<" data: " <<snapshot.Get("name").string_value()                <<std::endl;}else{std::cout <<source <<" data: null" <<std::endl;}}else{std::cout <<"Listen failed: " <<error <<std::endl;}});
Node.js
// Not yet supported in the Node.js client library
Go
// Not yet supported in the Go client library
PHP
// Not supported in the PHP client library
Unity
DocumentReferencedocRef=db.Collection("cities").Document("SF");docRef.Listen(snapshot=>{stringsource=(snapshot!=null &&snapshot.Metadata.HasPendingWrites)?"Local":"Server";stringsnapshotData="null";if(snapshot!=null &&snapshot.Exists){System.Text.StringBuilderbuilder=newSystem.Text.StringBuilder();IDictionary<string,object>dict=snapshot.ToDictionary();foreach(varKVPairindict){builder.Append($"{KVPair.Key}: {KVPair.Value}\n");}snapshotData=builder.ToString();}Debug.Log($"{source} data: ${snapshotData}");});
C#
// Not yet supported in the C# client library
Ruby
//NotyetsupportedintheRubyclientlibrary

Events for metadata changes

When listening for changes to a document, collection, or query, you can passoptions to control the granularity of events that your listener will receive.

By default, listeners are not notified of changes that only affect metadata.Consider what happens when your app writes a new document:

  1. A change event is immediately fired with the new data. The document has not yet been written to the backend so the "pending writes" flag istrue.
  2. The document is written to the backend.
  3. The backend notifies the client of the successful write. There is no change to the document data, but there is a metadata change because the "pending writes" flag is nowfalse.

If you want to receive snapshot events when the document or query metadatachanges, pass a listen options object when attaching your listener.

Note: You can pass listener options as shown in the following samples.You can also use the configuration interfaces for snapshot optionsdescribed below to explicitly configure events formetadata changes. For more information, see the reference documentation forKotlin + KTX Android,Java Android,Swift,Objective-C,andWeb modular.

Web

import{doc,onSnapshot}from"firebase/firestore";constunsub=onSnapshot(doc(db,"cities","SF"),{includeMetadataChanges:true},(doc)=>{// ...});

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
db.collection("cities").doc("SF").onSnapshot({// Listen for document metadata changesincludeMetadataChanges:true},(doc)=>{// ...});
Swift
Note: This product is not available on watchOS and App Clip targets.
// Listen to document metadata.db.collection("cities").document("SF").addSnapshotListener(includeMetadataChanges:true){documentSnapshot,errorin// ...}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
// Listen for metadata changes.[[[self.dbcollectionWithPath:@"cities"]documentWithPath:@"SF"]addSnapshotListenerWithIncludeMetadataChanges:YESlistener:^(FIRDocumentSnapshot*snapshot,NSError*error){// ...}];

Kotlin

// Listen for metadata changes to the document.valdocRef=db.collection("cities").document("SF")docRef.addSnapshotListener(MetadataChanges.INCLUDE){snapshot,e->// ...}

Java

// Listen for metadata changes to the document.DocumentReferencedocRef=db.collection("cities").document("SF");docRef.addSnapshotListener(MetadataChanges.INCLUDE,newEventListener<DocumentSnapshot>(){@OverridepublicvoidonEvent(@NullableDocumentSnapshotsnapshot,@NullableFirebaseFirestoreExceptione){// ...}});

Dart

finaldocRef=db.collection("cities").doc("SF");docRef.snapshots(includeMetadataChanges:true).listen((event){// ...});
Java
// Not yet supported in the Java client library
Python
//NotyetsupportedinPythonclientlibrary
C++
DocumentReferencedoc_ref=db->Collection("cities").Document("SF");doc_ref.AddSnapshotListener(MetadataChanges::kInclude,[](constDocumentSnapshot&snapshot,Errorerror,conststd::string&errorMsg){/* ... */});
Node.js
// Not yet supported the Node.js client library
Go
// Not yet supported in the Go client library
PHP
// Not supported in the PHP client library
Unity
DocumentReferencedocRef=db.Collection("cities").Document("SF");docRef.Listen(MetadataChanges.Include,snapshot=>{// ...});
C#
// Not yet supported in the C# client library
Ruby
//NotyetsupportedintheRubyclientlibrary
Note: If you just want to know when your write has completed, you can listen tothe completion callback rather than usinghasPendingWrites. In JavaScript, usethePromise returned from your write operation by attaching a.then()callback. In Swift, pass a completion callback to your write function.

Configure listeners for local changes only

Cloud Firestore snapshot listeners take an initial snapshot from the local cacheand concurrently fetch corresponding data from the server.

In some cases, you may not want follow-up fetches from the server. Client SDKsallow you to configure listeners to fire only with respect to data in the localcache. This helps you avoid unnecessary server calls and their costs, andleverage the client-side cache, which reflects local data and mutations.

Here, snapshot options are set in client code to allow listening for localchanges only.

Web

Learn more about the tree-shakeable modular Web API andupgrade from the namespaced API.
constunsubscribe=onSnapshot(doc(db,"cities","SF"),{includeMetadataChanges:true,source:'cache'},(documentSnapshot)=>{//});

Web

Learn more about the tree-shakeable modular Web API andupgrade from the namespaced API.
// Not yet supported in the Web namespaced API

Swift
Note: This product is not available on watchOS and App Clip targets.
// Set up listener optionsletoptions=SnapshotListenOptions().withSource(ListenSource.cache).withIncludeMetadataChanges(true)db.collection("cities").document("SF").addSnapshotListener(options:options){documentSnapshot,errorin// ...}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
// Set up listener optionsFIRSnapshotListenOptions*options=[[FIRSnapshotListenOptionsalloc]init];FIRSnapshotListenOptions*optionsWithSourceAndMetadata=[[optionsoptionsWithIncludeMetadataChanges:YES]optionsWithSource:FIRListenSourceCache];[[[self.dbcollectionWithPath:@"cities"]documentWithPath:@"SF"]addSnapshotListenerWithOptions:optionsWithSourceAndMetadatalistener:^(FIRDocumentSnapshot*snapshot,NSError*error){//…}];

Kotlin

// Set up listener optionsvaloptions=SnapshotListenOptions.Builder().setMetadataChanges(MetadataChanges.INCLUDE).setSource(ListenSource.CACHE).build();db.collection("cities").document("SF").addSnapshotListener(options){snapshot,error->//…}

Java

// Set up listener optionsSnapshotListenOptionsoptions=newSnapshotListenOptions.Builder().setMetadataChanges(MetadataChanges.INCLUDE).setSource(ListenSource.CACHE).build();db.collection("cities").document("SF").addSnapshotListener(options,newEventListener<DocumentSnapshot>(){//…});

Dart

// Not yet supported in this client library

Java
#NotyetsupportedintheJavaclientlibrary
Python
//NotyetsupportedinPythonclientlibrary
C++
// Not yet supported in the C++ client library
Node.js
// Not yet supported in the Node.js client library
Go
// Not yet supported in the Go client library
PHP
// Not yet supported in the PHP client library
Unity
// Not yet supported in the Unity client library
C#
// Not yet supported in the C# client library
Ruby
//NotyetsupportedintheRubyclientlibrary

Listen to multiple documents in a collection

As with documents, you can useonSnapshot() instead ofget() to listen to theresults of a query. This creates a query snapshot. For example, to listen to thedocuments with stateCA:

Web

import{collection,query,where,onSnapshot}from"firebase/firestore";constq=query(collection(db,"cities"),where("state","==","CA"));constunsubscribe=onSnapshot(q,(querySnapshot)=>{constcities=[];querySnapshot.forEach((doc)=>{cities.push(doc.data().name);});console.log("Current cities in CA: ",cities.join(", "));});

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
db.collection("cities").where("state","==","CA").onSnapshot((querySnapshot)=>{varcities=[];querySnapshot.forEach((doc)=>{cities.push(doc.data().name);});console.log("Current cities in CA: ",cities.join(", "));});
Swift
Note: This product is not available on watchOS and App Clip targets.
db.collection("cities").whereField("state",isEqualTo:"CA").addSnapshotListener{querySnapshot,erroringuardletdocuments=querySnapshot?.documentselse{print("Error fetching documents:\(error!)")return}letcities=documents.compactMap{$0["name"]}print("Current cities in CA:\(cities)")}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
[[[self.dbcollectionWithPath:@"cities"]queryWhereField:@"state"isEqualTo:@"CA"]addSnapshotListener:^(FIRQuerySnapshot*snapshot,NSError*error){if(snapshot==nil){NSLog(@"Error fetching documents: %@",error);return;}NSMutableArray*cities=[NSMutableArrayarray];for(FIRDocumentSnapshot*documentinsnapshot.documents){[citiesaddObject:document.data[@"name"]];}NSLog(@"Current cities in CA: %@",cities);}];

Kotlin

db.collection("cities").whereEqualTo("state","CA").addSnapshotListener{value,e->if(e!=null){Log.w(TAG,"Listen failed.",e)return@addSnapshotListener}valcities=ArrayList<String>()for(docinvalue!!){doc.getString("name")?.let{cities.add(it)}}Log.d(TAG,"Current cites in CA:$cities")}

Java

db.collection("cities").whereEqualTo("state","CA").addSnapshotListener(newEventListener<QuerySnapshot>(){@OverridepublicvoidonEvent(@NullableQuerySnapshotvalue,@NullableFirebaseFirestoreExceptione){if(e!=null){Log.w(TAG,"Listen failed.",e);return;}List<String>cities=newArrayList<>();for(QueryDocumentSnapshotdoc:value){if(doc.get("name")!=null){cities.add(doc.getString("name"));}}Log.d(TAG,"Current cites in CA: "+cities);}});

Dart

db.collection("cities").where("state",isEqualTo:"CA").snapshots().listen((event){finalcities=[];for(vardocinevent.docs){cities.add(doc.data()["name"]);}print("cities in CA:${cities.join(", ")}");});
Java
db.collection("cities").whereEqualTo("state","CA").addSnapshotListener(newEventListener<QuerySnapshot>(){@OverridepublicvoidonEvent(@NullableQuerySnapshotsnapshots,@NullableFirestoreExceptione){if(e!=null){System.err.println("Listen failed:"+e);return;}List<String>cities=newArrayList<>();for(DocumentSnapshotdoc:snapshots){if(doc.get("name")!=null){cities.add(doc.getString("name"));}}System.out.println("Current cites in CA: "+cities);}});
Python
# Create an Event for notifying main thread.callback_done=threading.Event()# Create a callback on_snapshot function to capture changesdefon_snapshot(col_snapshot,changes,read_time):print("Callback received query snapshot.")print("Current cities in California:")fordocincol_snapshot:print(f"{doc.id}")callback_done.set()col_query=db.collection("cities").where(filter=FieldFilter("state","==","CA"))# Watch the collection queryquery_watch=col_query.on_snapshot(on_snapshot)
C++
db->Collection("cities").WhereEqualTo("state",FieldValue::String("CA")).AddSnapshotListener([](constQuerySnapshot&snapshot,Errorerror,conststd::string&errorMsg){if(error==Error::kErrorOk){std::vector<std::string>cities;std::cout <<"Current cities in CA: " <<error <<std::endl;for(constDocumentSnapshot&doc:snapshot.documents()){cities.push_back(doc.Get("name").string_value());std::cout <<"" <<cities.back() <<std::endl;}}else{std::cout <<"Listen failed: " <<error <<std::endl;}});
Node.js
constquery=db.collection('cities').where('state','==','CA');constobserver=query.onSnapshot(querySnapshot=>{console.log(`Received query snapshot of size${querySnapshot.size}`);// ...},err=>{console.log(`Encountered error:${err}`);});
Go
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/api/iterator""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenMultiple listens to a query, returning the names of all cities// for a state.funclistenMultiple(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Where("state","==","CA").Snapshots(ctx)for{snap,err:=it.Next()// DeadlineExceeded will be returned when ctx is cancelled.ifstatus.Code(err)==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}ifsnap!=nil{for{doc,err:=snap.Documents.Next()iferr==iterator.Done{break}iferr!=nil{returnfmt.Errorf("Documents.Next: %w",err)}fmt.Fprintf(w,"Current cities in California: %v\n",doc.Ref.ID)}}}}
PHP
// Not supported in the PHP client library
Unity
Queryquery=db.Collection("cities").WhereEqualTo("State","CA");ListenerRegistrationlistener=query.Listen(snapshot=>{Debug.Log("Callback received query snapshot.");Debug.Log("Current cities in California:");foreach(DocumentSnapshotdocumentSnapshotinsnapshot.Documents){Debug.Log(documentSnapshot.Id);}});
C#
CollectionReferencecitiesRef=db.Collection("cities");Queryquery=db.Collection("cities").WhereEqualTo("State","CA");FirestoreChangeListenerlistener=query.Listen(snapshot=>{Console.WriteLine("Callback received query snapshot.");Console.WriteLine("Current cities in California:");foreach(DocumentSnapshotdocumentSnapshotinsnapshot.Documents){Console.WriteLine(documentSnapshot.Id);}});
Ruby
query=firestore.col(collection_path).where:state,:==,"CA"docs=[]# Watch the collection query.listener=query.listendo|snapshot|puts"Callback received query snapshot."puts"Current cities in California:"snapshot.docs.eachdo|doc|putsdoc.document_iddocs <<docendend

The snapshot handler will receive a new query snapshot every time the queryresults change (that is, when a document is added, removed, or modified).

Important: As explained above underEvents for localchanges, you will receive eventsimmediately for yourlocal writes. Your listener can use themetadata.hasPendingWrites field oneach document to determine whether the document has local changes that have notyet been written to the backend.

View changes between snapshots

It is often useful to see the actual changes to query results between querysnapshots, instead of simply using the entire query snapshot. For example, youmay want to maintain a cache as individual documents are added, removed, andmodified.

Web

import{collection,query,where,onSnapshot}from"firebase/firestore";constq=query(collection(db,"cities"),where("state","==","CA"));constunsubscribe=onSnapshot(q,(snapshot)=>{snapshot.docChanges().forEach((change)=>{if(change.type==="added"){console.log("New city: ",change.doc.data());}if(change.type==="modified"){console.log("Modified city: ",change.doc.data());}if(change.type==="removed"){console.log("Removed city: ",change.doc.data());}});});

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
db.collection("cities").where("state","==","CA").onSnapshot((snapshot)=>{snapshot.docChanges().forEach((change)=>{if(change.type==="added"){console.log("New city: ",change.doc.data());}if(change.type==="modified"){console.log("Modified city: ",change.doc.data());}if(change.type==="removed"){console.log("Removed city: ",change.doc.data());}});});
Swift
Note: This product is not available on watchOS and App Clip targets.
db.collection("cities").whereField("state",isEqualTo:"CA").addSnapshotListener{querySnapshot,erroringuardletsnapshot=querySnapshotelse{print("Error fetching snapshots:\(error!)")return}snapshot.documentChanges.forEach{diffinif(diff.type==.added){print("New city:\(diff.document.data())")}if(diff.type==.modified){print("Modified city:\(diff.document.data())")}if(diff.type==.removed){print("Removed city:\(diff.document.data())")}}}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
[[[self.dbcollectionWithPath:@"cities"]queryWhereField:@"state"isEqualTo:@"CA"]addSnapshotListener:^(FIRQuerySnapshot*snapshot,NSError*error){if(snapshot==nil){NSLog(@"Error fetching documents: %@",error);return;}for(FIRDocumentChange*diffinsnapshot.documentChanges){if(diff.type==FIRDocumentChangeTypeAdded){NSLog(@"New city: %@",diff.document.data);}if(diff.type==FIRDocumentChangeTypeModified){NSLog(@"Modified city: %@",diff.document.data);}if(diff.type==FIRDocumentChangeTypeRemoved){NSLog(@"Removed city: %@",diff.document.data);}}}];

Kotlin

db.collection("cities").whereEqualTo("state","CA").addSnapshotListener{snapshots,e->if(e!=null){Log.w(TAG,"listen:error",e)return@addSnapshotListener}for(dcinsnapshots!!.documentChanges){when(dc.type){DocumentChange.Type.ADDED->Log.d(TAG,"New city:${dc.document.data}")DocumentChange.Type.MODIFIED->Log.d(TAG,"Modified city:${dc.document.data}")DocumentChange.Type.REMOVED->Log.d(TAG,"Removed city:${dc.document.data}")}}}

Java

db.collection("cities").whereEqualTo("state","CA").addSnapshotListener(newEventListener<QuerySnapshot>(){@OverridepublicvoidonEvent(@NullableQuerySnapshotsnapshots,@NullableFirebaseFirestoreExceptione){if(e!=null){Log.w(TAG,"listen:error",e);return;}for(DocumentChangedc:snapshots.getDocumentChanges()){switch(dc.getType()){caseADDED:Log.d(TAG,"New city: "+dc.getDocument().getData());break;caseMODIFIED:Log.d(TAG,"Modified city: "+dc.getDocument().getData());break;caseREMOVED:Log.d(TAG,"Removed city: "+dc.getDocument().getData());break;}}}});

Dart

db.collection("cities").where("state",isEqualTo:"CA").snapshots().listen((event){for(varchangeinevent.docChanges){switch(change.type){caseDocumentChangeType.added:print("New City:${change.doc.data()}");break;caseDocumentChangeType.modified:print("Modified City:${change.doc.data()}");break;caseDocumentChangeType.removed:print("Removed City:${change.doc.data()}");break;}}});
Java
db.collection("cities").whereEqualTo("state","CA").addSnapshotListener(newEventListener<QuerySnapshot>(){@OverridepublicvoidonEvent(@NullableQuerySnapshotsnapshots,@NullableFirestoreExceptione){if(e!=null){System.err.println("Listen failed: "+e);return;}for(DocumentChangedc:snapshots.getDocumentChanges()){switch(dc.getType()){caseADDED:System.out.println("New city: "+dc.getDocument().getData());break;caseMODIFIED:System.out.println("Modified city: "+dc.getDocument().getData());break;caseREMOVED:System.out.println("Removed city: "+dc.getDocument().getData());break;default:break;}}}});
C++
db->Collection("cities").WhereEqualTo("state",FieldValue::String("CA")).AddSnapshotListener([](constQuerySnapshot&snapshot,Errorerror,conststd::string&errorMsg){if(error==Error::kErrorOk){for(constDocumentChange&dc:snapshot.DocumentChanges()){switch(dc.type()){caseDocumentChange::Type::kAdded:std::cout <<"New city: "                        <<dc.document().Get("name").string_value() <<std::endl;break;caseDocumentChange::Type::kModified:std::cout <<"Modified city: "                        <<dc.document().Get("name").string_value() <<std::endl;break;caseDocumentChange::Type::kRemoved:std::cout <<"Removed city: "                        <<dc.document().Get("name").string_value() <<std::endl;break;}}}else{std::cout <<"Listen failed: " <<error <<std::endl;}});
Python
# Create an Event for notifying main thread.delete_done=threading.Event()# Create a callback on_snapshot function to capture changesdefon_snapshot(col_snapshot,changes,read_time):print("Callback received query snapshot.")print("Current cities in California: ")forchangeinchanges:ifchange.type.name=="ADDED":print(f"New city:{change.document.id}")elifchange.type.name=="MODIFIED":print(f"Modified city:{change.document.id}")elifchange.type.name=="REMOVED":print(f"Removed city:{change.document.id}")delete_done.set()col_query=db.collection("cities").where(filter=FieldFilter("state","==","CA"))# Watch the collection queryquery_watch=col_query.on_snapshot(on_snapshot)
Node.js
constobserver=db.collection('cities').where('state','==','CA').onSnapshot(querySnapshot=>{querySnapshot.docChanges().forEach(change=>{if(change.type==='added'){console.log('New city: ',change.doc.data());}if(change.type==='modified'){console.log('Modified city: ',change.doc.data());}if(change.type==='removed'){console.log('Removed city: ',change.doc.data());}});});
Go
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenChanges listens to a query, returning the list of document changes.funclistenChanges(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Where("state","==","CA").Snapshots(ctx)for{snap,err:=it.Next()// DeadlineExceeded will be returned when ctx is cancelled.ifstatus.Code(err)==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}ifsnap!=nil{for_,change:=rangesnap.Changes{switchchange.Kind{casefirestore.DocumentAdded:fmt.Fprintf(w,"New city: %v\n",change.Doc.Data())casefirestore.DocumentModified:fmt.Fprintf(w,"Modified city: %v\n",change.Doc.Data())casefirestore.DocumentRemoved:fmt.Fprintf(w,"Removed city: %v\n",change.Doc.Data())}}}}}
PHP
// Not supported in the PHP client library
Unity
Queryquery=db.Collection("cities").WhereEqualTo("State","CA");ListenerRegistrationlistener=query.Listen(snapshot=>{foreach(DocumentChangechangeinsnapshot.GetChanges()){if(change.ChangeType==DocumentChange.Type.Added){Debug.Log(String.Format("New city: {0}",change.Document.Id));}elseif(change.ChangeType==DocumentChange.Type.Modified){Debug.Log(String.Format("Modified city: {0}",change.Document.Id));}elseif(change.ChangeType==DocumentChange.Type.Removed){Debug.Log(String.Format("Removed city: {0}",change.Document.Id));}}});
C#
CollectionReferencecitiesRef=db.Collection("cities");Queryquery=db.Collection("cities").WhereEqualTo("State","CA");FirestoreChangeListenerlistener=query.Listen(snapshot=>{foreach(DocumentChangechangeinsnapshot.Changes){if(change.ChangeType.ToString()=="Added"){Console.WriteLine("New city: {0}",change.Document.Id);}elseif(change.ChangeType.ToString()=="Modified"){Console.WriteLine("Modified city: {0}",change.Document.Id);}elseif(change.ChangeType.ToString()=="Removed"){Console.WriteLine("Removed city: {0}",change.Document.Id);}}});
Ruby
query=firestore.col(collection_path).where:state,:==,"CA"added=[]modified=[]removed=[]# Watch the collection query.listener=query.listendo|snapshot|puts"Callback received query snapshot."puts"Current cities in California:"snapshot.changes.eachdo|change|ifchange.added?puts"New city:#{change.doc.document_id}"added <<snapshotelsifchange.modified?puts"Modified city:#{change.doc.document_id}"modified <<snapshotelsifchange.removed?puts"Removed city:#{change.doc.document_id}"removed <<snapshotendendend
Important: The first query snapshot containsadded events for all existingdocuments that match the query. This is because you're getting a set of changesthat bring your query snapshot current with the initial state of the query. Thisallows you, for instance, to directly populate your UI from the changes youreceive in the first query snapshot, withoutneeding to add special logic for handling the initial state.

The initial state can come from the server directly, or from a localcache. If there is state available in a local cache, the query snapshot willbe initially populated with the cached data, then updated with theserver's data when the client has caught up with the server's state.

Detach a listener

When you are no longer interested in listening to your data, you must detachyour listener so that your event callbacks stop getting called. This allows theclient to stop using bandwidth to receive updates. For example:

Web

import{collection,onSnapshot}from"firebase/firestore";constunsubscribe=onSnapshot(collection(db,"cities"),()=>{// Respond to data// ...});// Later ...// Stop listening to changesunsubscribe();

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
varunsubscribe=db.collection("cities").onSnapshot(()=>{// Respond to data// ...});// Later ...// Stop listening to changesunsubscribe();
Swift
Note: This product is not available on watchOS and App Clip targets.
letlistener=db.collection("cities").addSnapshotListener{querySnapshot,errorin// ...}// ...// Stop listening to changeslistener.remove()
Objective-C
Note: This product is not available on watchOS and App Clip targets.
id<FIRListenerRegistration>listener=[[self.dbcollectionWithPath:@"cities"]addSnapshotListener:^(FIRQuerySnapshot*snapshot,NSError*error){// ...}];// ...// Stop listening to changes[listenerremove];

Kotlin

valquery=db.collection("cities")valregistration=query.addSnapshotListener{snapshots,e->// ...}// ...// Stop listening to changesregistration.remove()

Java

Queryquery=db.collection("cities");ListenerRegistrationregistration=query.addSnapshotListener(newEventListener<QuerySnapshot>(){// ...});// ...// Stop listening to changesregistration.remove();

Dart

finalcollection=db.collection("cities");finallistener=collection.snapshots().listen((event){// ...});listener.cancel();
Java
Queryquery=db.collection("cities");ListenerRegistrationregistration=query.addSnapshotListener(newEventListener<QuerySnapshot>(){// ...});// ...// Stop listening to changesregistration.remove();
Python
# Terminate watch on a documentdoc_watch.unsubscribe()
C++
// Add a listenerQueryquery=db->Collection("cities");ListenerRegistrationregistration=query.AddSnapshotListener([](constQuerySnapshot&snapshot,Errorerror,conststd::string&errorMsg){/* ... */});// Stop listening to changesregistration.Remove();
Node.js
constunsub=db.collection('cities').onSnapshot(()=>{});// ...// Stop listening for changesunsub();
Go
// Сontext with timeout stops listening to changes.ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()
PHP
// Not supported in the PHP client library
Unity
listener.Stop();
C#
awaitlistener.StopAsync();
Ruby
listener.stop

Handle listen errors

A listen may occasionally fail — for example, due to security permissions, or ifyou tried to listen on an invalid query. (Learn more aboutvalid and invalid queries.) To handle thesefailures, you can provide an error callback when you attach your snapshotlistener. After an error, the listener will not receive any more events, andthere is no need to detach your listener.

Web

import{collection,onSnapshot}from"firebase/firestore";constunsubscribe=onSnapshot(collection(db,"cities"),(snapshot)=>{// ...},(error)=>{// ...});

Web

Learn more about the tree-shakeable modular Web API and its advantages over the namespaced API.
db.collection("cities").onSnapshot((snapshot)=>{// ...},(error)=>{// ...});
Swift
Note: This product is not available on watchOS and App Clip targets.
db.collection("cities").addSnapshotListener{querySnapshot,errorinifleterror=error{print("Error retreiving collection:\(error)")}}
Objective-C
Note: This product is not available on watchOS and App Clip targets.
[[self.dbcollectionWithPath:@"cities"]addSnapshotListener:^(FIRQuerySnapshot*snapshot,NSError*error){if(error!=nil){NSLog(@"Error retreving collection: %@",error);}}];

Kotlin

db.collection("cities").addSnapshotListener{snapshots,e->if(e!=null){Log.w(TAG,"listen:error",e)return@addSnapshotListener}for(dcinsnapshots!!.documentChanges){if(dc.type==DocumentChange.Type.ADDED){Log.d(TAG,"New city:${dc.document.data}")}}}

Java

db.collection("cities").addSnapshotListener(newEventListener<QuerySnapshot>(){@OverridepublicvoidonEvent(@NullableQuerySnapshotsnapshots,@NullableFirebaseFirestoreExceptione){if(e!=null){Log.w(TAG,"listen:error",e);return;}for(DocumentChangedc:snapshots.getDocumentChanges()){if(dc.getType()==Type.ADDED){Log.d(TAG,"New city: "+dc.getDocument().getData());}}}});

Dart

finaldocRef=db.collection("cities");docRef.snapshots().listen((event)=>print("listener attached"),onError:(error)=>print("Listen failed:$error"),);
Java
db.collection("cities").addSnapshotListener(newEventListener<QuerySnapshot>(){@OverridepublicvoidonEvent(@NullableQuerySnapshotsnapshots,@NullableFirestoreExceptione){if(e!=null){System.err.println("Listen failed: "+e);return;}for(DocumentChangedc:snapshots.getDocumentChanges()){if(dc.getType()==Type.ADDED){System.out.println("New city: "+dc.getDocument().getData());}}}});
Python
//Snippetcomingsoon
C++
// Snippet coming soon.
Node.js
db.collection('cities').onSnapshot((snapshot)=>{//...},(error)=>{//...});
Go
import("context""fmt""io""time""cloud.google.com/go/firestore""google.golang.org/grpc/codes""google.golang.org/grpc/status")// listenErrors demonstrates how to handle listening errors.funclistenErrors(ctxcontext.Context,wio.Writer,projectID,collectionstring)error{// projectID := "project-id"ctx,cancel:=context.WithTimeout(ctx,30*time.Second)defercancel()client,err:=firestore.NewClient(ctx,projectID)iferr!=nil{returnfmt.Errorf("firestore.NewClient: %w",err)}deferclient.Close()it:=client.Collection(collection).Snapshots(ctx)for{snap,err:=it.Next()// Canceled will be returned when ctx is cancelled and DeadlineExceeded will// be returned when ctx reaches its deadline.ife:=status.Code(err);e==codes.Canceled||e==codes.DeadlineExceeded{returnnil}iferr!=nil{returnfmt.Errorf("Snapshots.Next: %w",err)}ifsnap!=nil{for_,change:=rangesnap.Changes{ifchange.Kind==firestore.DocumentAdded{fmt.Fprintf(w,"New city: %v\n",change.Doc.Data())}}}}}
PHP
// Not supported in the PHP client library
Unity
ListenerRegistrationregistration=db.Collection("cities").Listen(querySnapshot=>{// ...});registration.ListenerTask.ContinueWithOnMainThread(listenerTask=>{if(listenerTask.IsFaulted){Debug.LogError($"Listen failed: {listenerTask.Exception}");// ...// Handle the listener error.// ...}});
C#
// Snippet coming soon
Ruby
listener=firestore.col(collection_path).listendo|snapshot|snapshot.changes.eachdo|change|puts"New city:#{change.doc.document_id}"ifchange.added?endend# Register to be notified when unhandled errors occur.listener.on_errordo|error|puts"Listen failed:#{error.message}"end

Pricing

Pricing for realtime listener operations depends on the edition of the database.See the following:

What's next

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-02-18 UTC.