appwrite/appwrite · error · Exception
STORAGE_FILE_ALREADY_EXISTS
STORAGE_FILE_ALREADY_EXISTS
Error message
Storage file already exists
What it means
In the chunked/resumable file-upload flow, this is thrown when the client reports all chunks as uploaded (chunksUploaded equals chunksTotal) but the request carries no Content-Range header, so the server cannot finalize the upload. The endpoint interprets this as 'the file is already fully uploaded' and refuses the duplicate completion attempt with 409.
Source
Thrown at src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php:284
\ksort($parts);
$merged['parts'] = $parts;
$merged['chunks'] = \count($parts);
}
return $merged;
};
$prepareUpload = function () use ($authorization, $bucket, &$chunks, $contentRange, $dbForProject, $deviceForFiles, $fileId, $fileName, $fileSize, &$metadata, $folder, $path, $permissions, $response, &$completed): void {
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if (!$file->isEmpty()) {
$chunks = $file->getAttribute('chunksTotal', 1);
$uploaded = $file->getAttribute('chunksUploaded', 0);
$metadata = $file->getAttribute('metadata', []);
if ($uploaded === $chunks) {
if (empty($contentRange)) {
throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS);
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic($file, Response::MODEL_FILE);
$completed = true;
return;
}
}
if ($file->isEmpty()) {
$deviceForFiles->prepare($path, $metadata['content_type'] ?? '', $chunks, $metadata);
if (!empty($contentRange)) {
$doc = new Document([
'$id' => ID::custom($fileId),View on GitHub (pinned to ce3a85157f)
Solutions
- Treat 409 file_already_exists as success and GET /v1/storage/buckets/:bucketId/files/:fileId to confirm the completed file.
- Always send the Content-Range header on every chunk, including the final one.
- Use a fresh unique fileId if you actually intend a new upload.
- Serialize chunked uploads so only one worker drives the same fileId.
Example fix
// before: blind retry re-sends initial chunk without Content-Range
await storage.createFile({ bucketId, fileId, file });
// after: catch already-complete uploads
try {
await storage.createFile({ bucketId, fileId, file });
} catch (e) {
if (e.code === 409) return storage.getFile({ bucketId, fileId }); // already uploaded
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const f = await storage.getFile({ bucketId, fileId }).catch(() => null);
if (f && f.chunksUploaded === f.chunksTotal) {
return f; // upload already complete; do not re-send chunks
} Try / catch
try {
await uploadChunks(...);
} catch (e) {
if (e.code === 409 && e.type === 'file_already_exists') {
return storage.getFile({ bucketId, fileId }); // already fully uploaded
}
throw e;
} Prevention
- Send Content-Range on every chunk request, including the last.
- Before resuming, check chunksUploaded vs chunksTotal via getFile.
- Use unique fileIds per upload session; never share a fileId across workers.
- Mark upload sessions complete locally before the final chunk returns so retries skip it.
When it happens
Trigger: POST/PUT /v1/storage/buckets/:bucketId/files (or the chunked continuation) for a file whose uploaded chunk count already equals chunksTotal, sent without a contentRange header — i.e. attempting to re-upload or finalize an already-complete chunked upload.
Common situations: Retry frameworks re-sending the initial chunk request after all chunks were already uploaded; two upload workers racing on the same fileId; client SDK resuming an upload the server already finalized; custom upload loops that skip Content-Range on the final chunk.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- STORAGE_BUCKET_ALREADY_EXISTS
- general_server_error
- storage_bucket_not_found
- storage_bucket_not_found
- storage_file_not_found
AI-assisted analysis of appwrite/appwrite@ce3a85157f (2026-09-08).
Data as JSON: /api/errors/5e79c00266228782.
Report an issue: GitHub.