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
- Re-fetch the document with databases.getDocument, merge your changes onto the fresh copy, and retry the upsert
- Stop forwarding system attributes ($updatedAt/$createdAt) captured from older reads into the write payload
- 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
- Never send $createdAt/$updatedAt captured from older reads in write payloads
- Re-fetch immediately before writing when a document is known to be contended
- Serialize writes per documentId (queue or single writer) where last-writer-wins is unacceptable
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
- document_update_conflict
- document_update_conflict
- database_not_found
- collection_not_found
- general_bad_request
AI-assisted analysis of appwrite/appwrite@a1d520eea4 (2026-08-18).
Data as JSON: /api/errors/1acaa61d2824384b.
Report an issue: GitHub.