{"record":{"id":"1acaa61d2824384b","repo":"appwrite/appwrite","slug":"document-update-conflict-1acaa6","errorCode":"document_update_conflict","errorMessage":"Remote document is newer than local.","messagePattern":"Remote document is newer than local\\.","errorType":"exception","errorClass":"Appwrite\\Extend\\Exception","httpStatus":409,"severity":"error","filePath":"src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php","lineNumber":343,"sourceCode":"            $response\n                ->setStatusCode(SwooleResponse::STATUS_CODE_CREATED)\n                ->dynamic($mockDocument, $this->getResponseModel());\n            return;\n        }\n\n        $upserted = [];\n        try {\n            $dbForDatabases->withPreserveDates(function () use (&$upserted, $dbForDatabases, $collectionTableId, $newDocument) {\n                return $dbForDatabases->upsertDocuments(\n                    $collectionTableId,\n                    [$newDocument],\n                    onNext: function (Document $document) use (&$upserted) {\n                        $upserted[] = $document;\n                    },\n                );\n            });\n        } catch (ConflictException) {\n            throw new Exception($this->getConflictException());\n        } catch (UniqueException $e) {\n            throw new Exception($this->getUniqueConstraintException(), previous: $e);\n        } catch (DuplicateException $e) {\n            throw new Exception($this->getDuplicateException(), previous: $e, params: [$documentId]);\n        } catch (RelationshipException $e) {\n            throw new Exception(Exception::RELATIONSHIP_VALUE_INVALID, $e->getMessage());\n        } catch (StructureException $e) {\n            throw new Exception($this->getStructureException(), $e->getMessage());\n        }\n\n        $collectionsCache = [];\n\n        if (empty($upserted[0])) {\n            $upserted[0] = $dbForDatabases->getDocument($collectionTableId, $documentId);\n        }\n\n        $document = $upserted[0];\n","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/appwrite/appwrite/blob/a1d520eea492ebd1da1ef2dc8db5b2b34e277dce/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php#L325-L361","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before -- upserting a stale copy\nawait databases.upsertDocument(dbId, collId, docId, staleData);\n\n// after -- re-fetch, merge, retry\nconst fresh = await databases.getDocument(dbId, collId, docId);\nconst merged = { ...fresh, ...data };\ndelete merged.$createdAt;\ndelete merged.$updatedAt;\nawait databases.upsertDocument(dbId, collId, docId, merged);","handlingStrategy":"retry","validationCode":"async function upsertFresh(databases: Databases, dbId: string, collId: string, docId: string, changes: Record<string, unknown>): Promise<unknown> {\n  const fresh = await databases.getDocument(dbId, collId, docId);\n  const merged = { ...changes };\n  return databases.upsertDocument(dbId, collId, docId, merged);\n}","typeGuard":"function isStaleCopy(local: { $updatedAt: string }, remote: { $updatedAt: string }): boolean {\n  return new Date(remote.$updatedAt).getTime() > new Date(local.$updatedAt).getTime();\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    return await databases.upsertDocument(dbId, collId, docId, data);\n  } catch (e) {\n    if (!(e instanceof AppwriteException) || e.code !== 'document_update_conflict') throw e;\n    const fresh = await databases.getDocument(dbId, collId, docId);\n    data = { ...data, ...Object.fromEntries(Object.entries(fresh).filter(([k]) => k.startsWith('$'))) };\n  }\n}\nthrow new Error('upsert kept conflicting after 3 attempts');","preventionTips":["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"],"tags":["appwrite","databases","documents","conflict","concurrency","upsert"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"a1d520eea492ebd1da1ef2dc8db5b2b34e277dce","analyzedAt":"2026-08-18T21:34:56.994Z","contentChangedAt":"2026-08-18T21:34:56.994Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}