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

  1. Treat 409 file_already_exists as success and GET /v1/storage/buckets/:bucketId/files/:fileId to confirm the completed file.
  2. Always send the Content-Range header on every chunk, including the final one.
  3. Use a fresh unique fileId if you actually intend a new upload.
  4. 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

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


AI-assisted analysis of appwrite/appwrite@ce3a85157f (2026-09-08). Data as JSON: /api/errors/5e79c00266228782. Report an issue: GitHub.