Types for document and query snapshots that represent data retrieved from Firestore, including metadata and change tracking.
Snapshot of a Firestore document that may or may not exist.
class DocumentSnapshot<T = DocumentData, T2 extends DocumentData = DocumentData> {
protected constructor();
/** Whether the document exists */
readonly exists: boolean;
/** Reference to the document */
readonly ref: DocumentReference<T>;
/** Document ID */
readonly id: string;
/** Snapshot metadata */
readonly metadata: SnapshotMetadata;
/** Get document data, undefined if document doesn't exist */
data(options?: SnapshotOptions): T | undefined;
/** Get a specific field value */
get(fieldPath: string | FieldPath, options?: SnapshotOptions): any;
/** Check if this snapshot equals another */
isEqual(other: DocumentSnapshot<T>): boolean;
}Snapshot of a document that is guaranteed to exist.
class QueryDocumentSnapshot<T = DocumentData, T2 extends DocumentData = DocumentData> extends DocumentSnapshot<T, T2> {
private constructor();
/** Get document data, guaranteed to return data (never undefined) */
data(options?: SnapshotOptions): T;
}Result of a Firestore query containing multiple documents.
class QuerySnapshot<T = DocumentData, T2 extends DocumentData = DocumentData> {
private constructor();
/** Query that produced this snapshot */
readonly query: Query<T>;
/** Snapshot metadata */
readonly metadata: SnapshotMetadata;
/** Array of document snapshots */
readonly docs: Array<QueryDocumentSnapshot<T>>;
/** Number of documents in the snapshot */
readonly size: number;
/** Whether the snapshot is empty */
readonly empty: boolean;
/** Get document changes since last snapshot */
docChanges(options?: SnapshotListenOptions): Array<DocumentChange<T>>;
/** Iterate over all documents */
forEach(
callback: (result: QueryDocumentSnapshot<T>) => void,
thisArg?: any
): void;
/** Check if this snapshot equals another */
isEqual(other: QuerySnapshot<T>): boolean;
}Types for tracking changes in query results.
type DocumentChangeType = 'added' | 'removed' | 'modified';
interface DocumentChange<T = DocumentData, T2 extends DocumentData = DocumentData> {
/** Type of change */
readonly type: DocumentChangeType;
/** Document that changed */
readonly doc: QueryDocumentSnapshot<T>;
/** Previous index in the snapshot (-1 for 'added') */
readonly oldIndex: number;
/** New index in the snapshot (-1 for 'removed') */
readonly newIndex: number;
}Information about how a snapshot was obtained.
interface SnapshotMetadata {
/** Whether the snapshot has pending writes */
readonly hasPendingWrites: boolean;
/** Whether the snapshot came from cache */
readonly fromCache: boolean;
/** Check if this metadata equals another */
isEqual(other: SnapshotMetadata): boolean;
}Configuration for reading snapshot data.
interface SnapshotOptions {
/** How to handle server timestamps during reads */
readonly serverTimestamps?: 'estimate' | 'previous' | 'none';
}
interface SnapshotListenOptions {
/** Include metadata-only changes in listeners */
readonly includeMetadataChanges?: boolean;
}import type { DocumentSnapshot, SnapshotOptions } from "@firebase/firestore-types";
interface User {
name: string;
email: string;
lastLogin?: Timestamp;
}
function handleDocumentSnapshot(snapshot: DocumentSnapshot<User>) {
console.log('Document ID:', snapshot.id);
console.log('Document exists:', snapshot.exists);
console.log('From cache:', snapshot.metadata.fromCache);
console.log('Has pending writes:', snapshot.metadata.hasPendingWrites);
if (snapshot.exists) {
// Get all data
const userData = snapshot.data();
console.log('User name:', userData?.name);
// Get specific field
const email = snapshot.get('email');
console.log('Email:', email);
// Handle server timestamps
const options: SnapshotOptions = { serverTimestamps: 'estimate' };
const dataWithEstimates = snapshot.data(options);
console.log('Last login (estimated):', dataWithEstimates?.lastLogin);
}
}import type { QuerySnapshot, QueryDocumentSnapshot } from "@firebase/firestore-types";
function processQuerySnapshot(snapshot: QuerySnapshot<User>) {
console.log(`Found ${snapshot.size} users`);
console.log('Query is empty:', snapshot.empty);
console.log('From cache:', snapshot.metadata.fromCache);
// Process all documents
snapshot.forEach((doc: QueryDocumentSnapshot<User>) => {
const user = doc.data(); // Guaranteed to exist
console.log(`User ${doc.id}: ${user.name} (${user.email})`);
});
// Or use the docs array directly
const users = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
return users;
}import type { DocumentChange, DocumentChangeType } from "@firebase/firestore-types";
function handleQueryChanges(snapshot: QuerySnapshot<User>) {
const changes = snapshot.docChanges();
changes.forEach((change: DocumentChange<User>) => {
const user = change.doc.data();
switch (change.type) {
case 'added':
console.log(`New user added at index ${change.newIndex}:`);
console.log(` ${user.name} (${user.email})`);
break;
case 'modified':
console.log(`User modified, moved from ${change.oldIndex} to ${change.newIndex}:`);
console.log(` ${user.name} (${user.email})`);
break;
case 'removed':
console.log(`User removed from index ${change.oldIndex}:`);
console.log(` ${user.name} (${user.email})`);
break;
}
});
}import type { SnapshotListenOptions } from "@firebase/firestore-types";
function monitorCollection(collection: CollectionReference<User>) {
// Include metadata changes in listener
const options: SnapshotListenOptions = {
includeMetadataChanges: true
};
const unsubscribe = collection.onSnapshot(options, {
next: (snapshot: QuerySnapshot<User>) => {
if (snapshot.metadata.hasPendingWrites) {
console.log('Local changes pending...');
}
if (snapshot.metadata.fromCache) {
console.log('Data loaded from cache');
} else {
console.log('Data loaded from server');
}
// Process document changes
handleQueryChanges(snapshot);
},
error: (error) => {
console.error('Snapshot listener error:', error);
}
});
return unsubscribe;
}function compareSnapshots(
oldSnapshot: DocumentSnapshot<User>,
newSnapshot: DocumentSnapshot<User>
) {
// Check if snapshots are equal
if (oldSnapshot.isEqual(newSnapshot)) {
console.log('Snapshots are identical');
return;
}
// Check if existence changed
if (oldSnapshot.exists !== newSnapshot.exists) {
if (newSnapshot.exists) {
console.log('Document was created');
} else {
console.log('Document was deleted');
}
return;
}
// Compare data if both exist
if (oldSnapshot.exists && newSnapshot.exists) {
const oldData = oldSnapshot.data();
const newData = newSnapshot.data();
console.log('Document was modified');
console.log('Old data:', oldData);
console.log('New data:', newData);
}
}