Create and update documents

targeted, strict document creation

On the admin SDK, we can perform strict targeted document creations, aka create a document with a controlled ID and expect a failure if the document already exists:

docRef.create(data)

The client SDK doesn't offer an equivalent function. Instead, we can do a two-step transaction where we read for document existence then write the document conditionally.

random-ID document creation (add)

The client SDK prefers random-ID creation that always succeed because the document won't already exist by design (add):

addDoc(collectionRef, data)
// db.collection("message").add(data)

upsert (set)

An upsert works regardless if a document exists or not. It is destructive, as it override any existing document.

setDoc(docRef, data)
// docRef.set(data)

update documents

We assume the document already exists: we use the update pattern or the set with merge pattern.

The update function is a strict update: it correctly fails if the document doesn't exist.

Both update and set merge expect a change object. We type the change as a Partial or as a Pick of the document:

const change: Partial<User> = { displayName: "Johnny Appleseed" }
updateDoc(docRef, change)
// docRef.update(change)

update uses the provided fields to replace the existing ones, the other fields being left unchanged.

To mutate a single field in an object field, we target it with dot notation. If we target the object instead, the omitted sub-fields are deleted (different from set with merge)

// sub-field
const change: Partial<User> = { "address.city": "Lyon" }
updateDoc(docRef, change)

If TypeScript complains about the dot notation, we use the FieldPath overload:

updateDoc(docRef, new FieldPath("address", "city"), "Lyon")

partial update with set and merge

the merge option changes the meaning of set: we are now providing a change object, not the new object.

What we target in the change object is what gets changed, including sub-fields. The omitted sub-fields are preserved (deep merge): there is no need for dot notation:

const change = { address: { city: "Lyon" } } // only changes city in address

setDoc(docRef, change, { merge: true })
// docRef.set(data, { merge: true })

relative change (increment, decrement)

We ask the server to change the field by n, which can be positive or negative. The current value is unknown:

const change = {
    activityScore: increment(1), // or e.g. -1
}

// docRef.update({
//    count: FieldValue.increment(1),
// })

delete field

We ask the server to delete a field. This shortcuts the need to fetch the document first and store it second omitting the given field:

updateDoc(docRef, {
    fleet: deleteField(),
})

// docRef.update({
//    fleet: FieldValue.delete(),
// })

server timestamp field

Ask the server to generate a Firestore timestamp value.

updateDoc(docRef, {
    count: serverTimestamp(),
})

// docRef.update({
//    count: FieldValue.serverTimestamp(),
// })

delete document

deleteDoc(docRef)
// docRef.delete()
earlymorning logo

Create and update documents

targeted, strict document creation

On the admin SDK, we can perform strict targeted document creations, aka create a document with a controlled ID and expect a failure if the document already exists:

docRef.create(data)

The client SDK doesn't offer an equivalent function. Instead, we can do a two-step transaction where we read for document existence then write the document conditionally.

random-ID document creation (add)

The client SDK prefers random-ID creation that always succeed because the document won't already exist by design (add):

addDoc(collectionRef, data)
// db.collection("message").add(data)

upsert (set)

An upsert works regardless if a document exists or not. It is destructive, as it override any existing document.

setDoc(docRef, data)
// docRef.set(data)

update documents

We assume the document already exists: we use the update pattern or the set with merge pattern.

The update function is a strict update: it correctly fails if the document doesn't exist.

Both update and set merge expect a change object. We type the change as a Partial or as a Pick of the document:

const change: Partial<User> = { displayName: "Johnny Appleseed" }
updateDoc(docRef, change)
// docRef.update(change)

update uses the provided fields to replace the existing ones, the other fields being left unchanged.

To mutate a single field in an object field, we target it with dot notation. If we target the object instead, the omitted sub-fields are deleted (different from set with merge)

// sub-field
const change: Partial<User> = { "address.city": "Lyon" }
updateDoc(docRef, change)

If TypeScript complains about the dot notation, we use the FieldPath overload:

updateDoc(docRef, new FieldPath("address", "city"), "Lyon")

partial update with set and merge

the merge option changes the meaning of set: we are now providing a change object, not the new object.

What we target in the change object is what gets changed, including sub-fields. The omitted sub-fields are preserved (deep merge): there is no need for dot notation:

const change = { address: { city: "Lyon" } } // only changes city in address

setDoc(docRef, change, { merge: true })
// docRef.set(data, { merge: true })

relative change (increment, decrement)

We ask the server to change the field by n, which can be positive or negative. The current value is unknown:

const change = {
    activityScore: increment(1), // or e.g. -1
}

// docRef.update({
//    count: FieldValue.increment(1),
// })

delete field

We ask the server to delete a field. This shortcuts the need to fetch the document first and store it second omitting the given field:

updateDoc(docRef, {
    fleet: deleteField(),
})

// docRef.update({
//    fleet: FieldValue.delete(),
// })

server timestamp field

Ask the server to generate a Firestore timestamp value.

updateDoc(docRef, {
    count: serverTimestamp(),
})

// docRef.update({
//    count: FieldValue.serverTimestamp(),
// })

delete document

deleteDoc(docRef)
// docRef.delete()