Skip to content

ID Strategies

The Firestore document name is your document’s identity (see Document Identity). Choosing how that name is assigned is a modeling decision. This page covers the three common strategies and how to write each.

Let Firestore assign a random id. Use create(data) — it generates the id and returns { id } (or the document with { returnDoc: true }). This is the right default for entities with no natural key (users created by sign-up, orders, events):

const { id } = await orderRepo.create({ total: 42, status: 'pending' });

If you need the id before writing — e.g. to reference it from another document in the same request — mint one with repo.newId() and write under it with upsert:

const orderId = orderRepo.newId();
await invoiceRepo.upsert(invoiceId, { orderId }); // reference it first
await orderRepo.upsert(orderId, { total: 42, status: 'pending' });

When a document has a natural key — a slug, an external system’s id, a singleton config document — use that value as the id. Choose the write method by what a re-run should do:

  • createWithId(id, data) — create-only. Claims the id exactly once; a second write (or a concurrent claim) raises ConflictError and leaves the stored document untouched. Use this when the id must be claimed (an idempotency key, an upstream record id, an email hash).
  • upsert(id, data) — create-or-overwrite. Re-running is idempotent and overwrites. Use this when the latest write should win (a singleton config, a mirrored external record).
import { ConflictError } from 'flintfire';
// Claim an externally-derived id exactly once
try {
await productRepo.createWithId(`sku-${sku}`, { name, price });
} catch (error) {
if (error instanceof ConflictError) {
// Already claimed — do not overwrite.
}
}
// A singleton config document at a fixed id (re-runs overwrite)
await configRepo.upsert('app-config', { featureFlags: { darkMode: true } });
// A record keyed by an external id where the latest snapshot should win
await productRepo.upsert(`sku-${sku}`, { name, price });

Validate any externally-sourced id at the boundary with repo.id(rawKey) before using it as a document name — see Document Identity. For the full create-only / precondition surface, see Conditional writes.

To model a 1:1 relationship, store two documents under the same id in different collections (a users/{uid} profile and a userSettings/{uid} document). Reads become direct getById(uid) lookups on either collection — no query, no join. Write the secondary document with upsert(uid, …) so it lands at the shared id.

Strategy Write method Use when
Auto-generated create(data) No natural key; the common case
Claim once createWithId(id, …) A natural key that must not be overwritten on collision
Deterministic upsert(id, data) A natural key / slug / external id; re-runs overwrite
Singleton upsert(id, data) Exactly one document (config, counters)
Shared (1:1) upsert(id, data) A parallel document keyed by another collection’s id

See CRUD Operations for the full write surface and FirestoreRepository for signatures.