Create and update documents

strict document creation

Strictly create a document with a controlled ID. The operation aborts if a document exists. (admin SDK only)

docRef.create(data)

The client SDK wants to be offline friendly. As such, It doesn't support strict document creation with a controlled ID because it requires a server roundtrip to green-light it. It does support random ID creation because the document won't exist by design:

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

To get a controlled, strict document creation, we must use a two-steps transaction where we first read then write and throw if a document exists.

upsert

An upsert works regardless if a document exists or not, and has the same result (idempotent). It is destructive, aka override any existing document: It has the effect of a creation:

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

partial update

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

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

Both update and set merge expect a change object.

For update, the change fields replace the existing ones as-provided, the other fields are unchanged.

If we want to mutate a single property within an object field (aka mutate a sub-field), we target the sub-field directly, with a dot notation field:

const change = { displayName: "Johnny Appleseed" }
updateDoc(docRef, data)
docRef.update(data)

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

Note: We type the change as a Partial or a Pick of the document. If TypeScript complains about the dot notation, we use a separate version of updateDoc():

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

partial update with set

set comes with a merge option that changes its meaning: we are now providing a change object. The risk is to forget the merge option and override the document with a change object.

We provide the sub-fields we want to change. The other ones are preserved (deep merge):

const change = { address: { city: "Lyon" } } // it preserves the country field

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

blind increment

We ask the server to increment the field by n, which may be negative for decrement. We skip a preemptive read since we don't care about the absolute value:

const partialUserDoc = {
    activityScore: increment(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

docRef.delete()
deleteDoc(docRef)
earlymorning logo

© Antoine Weber 2026 - All rights reserved

Create and update documents

strict document creation

Strictly create a document with a controlled ID. The operation aborts if a document exists. (admin SDK only)

docRef.create(data)

The client SDK wants to be offline friendly. As such, It doesn't support strict document creation with a controlled ID because it requires a server roundtrip to green-light it. It does support random ID creation because the document won't exist by design:

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

To get a controlled, strict document creation, we must use a two-steps transaction where we first read then write and throw if a document exists.

upsert

An upsert works regardless if a document exists or not, and has the same result (idempotent). It is destructive, aka override any existing document: It has the effect of a creation:

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

partial update

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

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

Both update and set merge expect a change object.

For update, the change fields replace the existing ones as-provided, the other fields are unchanged.

If we want to mutate a single property within an object field (aka mutate a sub-field), we target the sub-field directly, with a dot notation field:

const change = { displayName: "Johnny Appleseed" }
updateDoc(docRef, data)
docRef.update(data)

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

Note: We type the change as a Partial or a Pick of the document. If TypeScript complains about the dot notation, we use a separate version of updateDoc():

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

partial update with set

set comes with a merge option that changes its meaning: we are now providing a change object. The risk is to forget the merge option and override the document with a change object.

We provide the sub-fields we want to change. The other ones are preserved (deep merge):

const change = { address: { city: "Lyon" } } // it preserves the country field

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

blind increment

We ask the server to increment the field by n, which may be negative for decrement. We skip a preemptive read since we don't care about the absolute value:

const partialUserDoc = {
    activityScore: increment(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

docRef.delete()
deleteDoc(docRef)