pubkey/rxdb · error · RxError

CONFLICT

CONFLICT

Error message

        RxDB Error-Code: ${message}.
        Hint: Error messages are not included in RxDB core to reduce build size.
        To show the full error messages and to ensure that you do not make any mistakes when using RxDB,
        use the dev-mode plugin when you are in development mode: https://rxdb.info/dev-mode.html?console=error
        
Find out more about this error here: https://rxdb.info/errors.html?console=errors#CONFLICT 
Still stuck? Ask in the RxDB Discord: https://rxdb.info/chat 

What it means

CONFLICT is thrown by throwIfIsStorageWriteError() when the underlying RxStorage returns a write error with HTTP-style status 409, meaning the write expected a document revision that no longer matches the currently stored one. This is RxDB's optimistic-concurrency check: another writer (another tab, device, or replication) changed the document between your read and your write. Callers include insert (already exists), upsert, _saveData and remove (deleted or changed in the meantime).

Source

Thrown at src/rx-storage-helper.ts:167

 */
export function stackCheckpoints<CheckpointType>(
    checkpoints: (CheckpointType | undefined)[]
): CheckpointType {
    return Object.assign(
        {},
        ...checkpoints.filter(x => !!x)
    );
}

export function throwIfIsStorageWriteError<RxDocType>(
    collection: RxCollection<RxDocType, any, any>,
    documentId: string,
    writeData: RxDocumentWriteData<RxDocType> | RxDocType,
    error: RxStorageWriteError<RxDocType> | undefined
) {
    if (error) {
        if (error.status === 409) {
            throw newRxError('CONFLICT', {
                collection: collection.name,
                id: documentId,
                writeError: error,
                data: writeData
            });
        } else if (error.status === 422) {
            throw newRxError('VD2', {
                collection: collection.name,
                id: documentId,
                writeError: error,
                data: writeData
            });
        } else {
            throw error;
        }
    }
}

View on GitHub (pinned to af6fb65f94)

Solutions

  1. Retry the operation: re-fetch the document, rebase your change on the new state, and write again (this is what incrementalUpsert/retry loops do).
  2. Use incrementalUpsert()/incrementalModify() instead of manual upsert so RxDB retries conflicts for you.
  3. For duplicate inserts, check existence first with collection.findOne(id).exec() or switch to upsert semantics intentionally.
  4. Handle the CONFLICT RxError in a catch block with a bounded retry and conflict-resolution logic, especially inside replication handlers.
  5. Ensure leader election is used so only one tab writes at a time.

Example fix

// before
await collection.upsert(doc); // may throw CONFLICT on revision mismatch
// after
import { firstValueFrom } from 'rxjs';
try {
  await collection.upsert(doc);
} catch (err) {
  if (err.code === 'CONFLICT') {
    const current = await collection.findOne(doc.id).exec();
    await collection.upsert({ ...doc, _rev: current?.revision });
  } else {
    throw err;
  }
}
Defensive patterns

Strategy: retry

Validate before calling

async function docExists(collection: RxCollection, id: string): Promise<boolean> {
  return (await collection.findOne(id).exec()) !== null;
}
// before insert: if (await docExists(collection, id)) use upsert or abort;

Type guard

function isConflictError(err: unknown): err is RxError {
  return err instanceof RxError && (err as any).code === 'CONFLICT';
}

Try / catch

import { RxError } from 'rxdb';
let attempts = 0;
while (true) {
  try {
    await collection.upsert(data);
    break;
  } catch (err) {
    if (isConflictError(err) && ++attempts < 5) {
      const current = await collection.findOne(data.id).exec();
      data = { ...data, _rev: current?.revision } as typeof data;
      continue; // rebase and retry
    }
    throw err;
  }
}

Prevention

When it happens

Trigger: collection.insert() when a document with the same primary key already exists; doc.remove() or save when the document was modified/deleted elsewhere since it was read; incrementalUpsert racing with a remote replication write; two tabs writing the same document without leader election.

Common situations: Multi-tab or multi-device usage where two clients edit the same document concurrently; replication pipelines applying remote changes between a local read and write; re-running a seed script that inserts existing ids; calling upsert expecting overwrite semantics when the storage layer still enforces revision checks.


AI-assisted analysis of pubkey/rxdb@af6fb65f94 (2026-08-31). Data as JSON: /api/errors/e7be4d735743bb70. Report an issue: GitHub.