appwrite/appwrite · error · Appwrite\Extend\Exception

document_update_conflict

document_update_conflict

Error message

Remote document is newer than local.

What it means

The database adapter threw a ConflictException inside upsertDocuments, which this endpoint maps to document_update_conflict ('Remote document is newer than local.'). Appwrite applies optimistic concurrency to document writes: when the stored document's revision is newer than the version the write is based on, the write is rejected rather than silently overwriting. The upsert runs under withPreserveDates, so a payload derived from a stale copy of the document makes the conflict explicit.

Source

Thrown at src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php:343

            $response
                ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)
                ->dynamic($mockDocument, $this->getResponseModel());
            return;
        }

        $upserted = [];
        try {
            $dbForDatabases->withPreserveDates(function () use (&$upserted, $dbForDatabases, $collectionTableId, $newDocument) {
                return $dbForDatabases->upsertDocuments(
                    $collectionTableId,
                    [$newDocument],
                    onNext: function (Document $document) use (&$upserted) {
                        $upserted[] = $document;
                    },
                );
            });
        } catch (ConflictException) {
            throw new Exception($this->getConflictException());
        } catch (UniqueException $e) {
            throw new Exception($this->getUniqueConstraintException(), previous: $e);
        } catch (DuplicateException $e) {
            throw new Exception($this->getDuplicateException(), previous: $e, params: [$documentId]);
        } catch (RelationshipException $e) {
            throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());
        } catch (StructureException $e) {
            throw new Exception($this->getStructureException(), $e->getMessage());
        }

        $collectionsCache = [];

        if (empty($upserted[0])) {
            $upserted[0] = $dbForDatabases->getDocument($collectionTableId, $documentId);
        }

        $document = $upserted[0];

View on GitHub (pinned to a1d520eea4)

Solutions

  1. Re-fetch the document with databases.getDocument, merge your changes onto the fresh copy, and retry the upsert
  2. Stop forwarding system attributes ($updatedAt/$createdAt) captured from older reads into the write payload
  3. If last-writer-wins is acceptable, serialize writes per documentId (queue or lock) so concurrent upserts cannot interleave

Example fix

// before -- upserting a stale copy
await databases.upsertDocument(dbId, collId, docId, staleData);

// after -- re-fetch, merge, retry
const fresh = await databases.getDocument(dbId, collId, docId);
const merged = { ...fresh, ...data };
delete merged.$createdAt;
delete merged.$updatedAt;
await databases.upsertDocument(dbId, collId, docId, merged);
Defensive patterns

Strategy: retry

Validate before calling

async function upsertFresh(databases: Databases, dbId: string, collId: string, docId: string, changes: Record<string, unknown>): Promise<unknown> {
  const fresh = await databases.getDocument(dbId, collId, docId);
  const merged = { ...changes };
  return databases.upsertDocument(dbId, collId, docId, merged);
}

Type guard

function isStaleCopy(local: { $updatedAt: string }, remote: { $updatedAt: string }): boolean {
  return new Date(remote.$updatedAt).getTime() > new Date(local.$updatedAt).getTime();
}

Try / catch

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await databases.upsertDocument(dbId, collId, docId, data);
  } catch (e) {
    if (!(e instanceof AppwriteException) || e.code !== 'document_update_conflict') throw e;
    const fresh = await databases.getDocument(dbId, collId, docId);
    data = { ...data, ...Object.fromEntries(Object.entries(fresh).filter(([k]) => k.startsWith('$'))) };
  }
}
throw new Error('upsert kept conflicting after 3 attempts');

Prevention

When it happens

Trigger: Two clients upserting the same documentId concurrently; a client upserting a document object fetched earlier while the remote copy has since been updated by someone else; retry logic resubmitting a stale payload after an earlier successful write.

Common situations: Multi-tab or multi-device editors; a server worker and a realtime-triggered function both writing the same document; a race between a webhook handler and the original API caller.

Related errors


AI-assisted analysis of appwrite/appwrite@a1d520eea4 (2026-08-18). Data as JSON: /api/errors/1acaa61d2824384b. Report an issue: GitHub.