How to fix StorageApiError: resource_already_exists in Supabase

SupabaseBEGINNERMEDIUMUpdated Dec 29, 2025· 150 views

This error occurs when attempting to upload a file to Supabase Storage at a path where a file already exists. By default, Supabase rejects file uploads that would overwrite existing resources. The solution is to enable the upsert option to allow file replacement or delete the existing file first.

Quick fix

Fixes 409

const { data, error } = await supabase.storage
  .from('bucket-name')
  .upload('path/to/file.jpg', file, {
    upsert: true  // Allows overwriting existing files
  });

if (error) {
  console.error('Upload error:', error.message);
} else {
  console.log('File uploaded successfully:', data);
}
  1. Enable upsert to overwrite existing files
  2. Check if file exists before uploading
  3. Use unique filenames with timestamps or UUIDs
  4. Handle upsert with proper RLS policies
supabase — error
const { data, error } = await supabase.storage
.from('bucket-name')
.upload('path/to/file.jpg', file, {
upsert: true // Allows overwriting existing files
});
if (error) {
console.error('Upload error:', error.message);
} else {
console.log('File uploaded successfully:', data);
}
StorageApiError: resource_already_exists

What this error means

The "Resource already exists" error (HTTP 409 Conflict) is thrown by Supabase Storage API when you attempt to upload a file to a location where a file already exists. Unlike some systems that overwrite files by default, Supabase's storage layer is designed to prevent accidental overwrites, making this an intentional safety mechanism. This error typically occurs during file upload operations and indicates that the specified file path is already occupied. It's distinct from bucket-level errors—the bucket exists and is accessible, but the specific file path is taken.

Typical symptoms

  • File upload fails with "resource_already_exists" or "409 Conflict" error
  • Error message shows "The resource already exists"
  • Upload operation fails when re-uploading the same file path
  • Error occurs inconsistently—only when file path already contains data
  • CI/CD pipelines fail during file deployment when running multiple times
  • Profile picture or avatar uploads fail on second attempt with same filename

Common causes

  1. Uploading a file to the same path without using the upsert option
  2. Missing upsert: true flag in file upload options
  3. Attempting to create a file at a path where another version exists
  4. Race condition in concurrent uploads to the same path
  5. Batch upload process running without checking for existing files
  6. File path being reused across different application deployments

How to fix "StorageApiError: resource_already_exists"

1Enable upsert to overwrite existing files

The simplest solution is to set upsert: true in your upload options. This tells Supabase to overwrite the file if it already exists:

javascript
const { data, error } = await supabase.storage
  .from('bucket-name')
  .upload('path/to/file.jpg', file, {
    upsert: true  // Allows overwriting existing files
  });

if (error) {
  console.error('Upload error:', error.message);
} else {
  console.log('File uploaded successfully:', data);
}

The upsert option is available in the FileOptions object for the upload() method. When set to true, if a file exists at the specified path, it will be replaced with the new file.

2Check if file exists before uploading

Alternatively, you can check whether a file already exists and handle it explicitly:

javascript
const filePath = 'path/to/file.jpg';

// List files to check if it exists
const { data: existingFiles, error: listError } = await supabase.storage
  .from('bucket-name')
  .list('path/to');

if (listError) {
  console.error('List error:', listError.message);
  return;
}

const fileExists = existingFiles.some(f => f.name === 'file.jpg');

if (fileExists) {
  // Delete the old file first
  const { error: deleteError } = await supabase.storage
    .from('bucket-name')
    .remove([filePath]);
  
  if (deleteError) {
    console.error('Delete error:', deleteError.message);
    return;
  }
}

// Now upload the new file
const { data, error } = await supabase.storage
  .from('bucket-name')
  .upload(filePath, file);

if (error) {
  console.error('Upload error:', error.message);
} else {
  console.log('File uploaded:', data);
}

This approach gives you explicit control and lets you log the deletion of old files.

3Use unique filenames with timestamps or UUIDs

For cases where you want to keep multiple versions or avoid overwrites entirely, generate unique filenames:

javascript
import { v4 as uuidv4 } from 'uuid';

const originalFileName = file.name;
const timestamp = new Date().getTime();
const fileExtension = originalFileName.split('.').pop();

// Option 1: Timestamp-based
const uniquePath = `uploads/${timestamp}-${originalFileName}`;

// Option 2: UUID-based (recommended)
const uniqueId = uuidv4();
const uniquePath = `uploads/${uniqueId}.${fileExtension}`;

const { data, error } = await supabase.storage
  .from('bucket-name')
  .upload(uniquePath, file);  // No upsert needed, path is always unique

if (error) {
  console.error('Upload error:', error.message);
} else {
  console.log('File uploaded:', data);
  return uniquePath;  // Return the unique path for storage
}

Store the returned unique path in your database so you can reference and download the file later.

4Handle upsert with proper RLS policies

When using upsert: true, ensure your Row Level Security policies allow UPDATE operations on storage.objects:

sql
-- In Supabase SQL Editor, add an UPDATE policy
create policy "Allow users to update their own files"
on storage.objects
for update
to authenticated
using ( auth.uid()::text = owner_id )
with check ( auth.uid()::text = owner_id );

-- Or for development (allow all - be careful in production)
create policy "Allow upsert for all users"
on storage.objects
for update
using ( true )
with check ( true );

Without an UPDATE policy, upsert operations will fail with a 403 Forbidden error. Ensure you have both INSERT and UPDATE policies for full upsert functionality.

5Debug with verbose logging in development

Add detailed logging to understand whether you're hitting the resource-exists error or a different issue:

javascript
async function uploadWithDebug(bucketName, filePath, file) {
  console.log(`Attempting upload to: ${bucketName}/${filePath}`);
  
  const { data, error } = await supabase.storage
    .from(bucketName)
    .upload(filePath, file, {
      upsert: false  // Set to false to see if file exists
    });

  if (error) {
    console.error('Upload failed:', {
      message: error.message,
      status: error.status,
      statusCode: error.statusCode,
      cause: error.cause
    });
    
    // Check if it's specifically the "resource already exists" error
    if (error.message.includes('resource_already_exists') || error.status === 409) {
      console.log('File already exists at this path. Retry with upsert: true');
      
      // Retry with upsert
      const { data: retryData, error: retryError } = await supabase.storage
        .from(bucketName)
        .upload(filePath, file, {
          upsert: true
        });
      
      if (retryError) {
        console.error('Upsert also failed:', retryError);
      } else {
        console.log('File upserted successfully:', retryData);
        return retryData;
      }
    }
  } else {
    console.log('File uploaded successfully:', data);
    return data;
  }
}

This logging helps identify whether the issue is truly a conflict or a different permission/policy problem.

Advanced notes

Upsert vs Update: The upsert: true option performs an INSERT or UPDATE operation automatically. If the file doesn't exist, it's inserted. If it exists, it's updated/replaced. This is more efficient than checking existence separately.

File Metadata Loss: When you overwrite a file using upsert, the new file does not inherit metadata from the previous version. If you've set custom metadata (cache-control, content-type, etc.) on the original file, you need to specify it again in the upsert call.

Race Conditions: In high-concurrency scenarios where multiple uploads to the same path occur simultaneously, using upsert with random filenames is safer than relying on sequential checks. Consider distributing writes across different paths when possible.

Storage Objects vs Signed URLs: Creating a signed URL with createSignedUploadUrl on an existing file path will also fail with the same error. If you're using signed URLs for uploads, you must either enable upsert or generate unique paths.

Cache Control and Content-Type: When upserting files, remember to explicitly set cache-control headers if needed:

javascript
await supabase.storage
  .from('bucket')
  .upload(path, file, {
    upsert: true,
    contentType: 'image/jpeg',
    cacheControl: '3600'  // 1 hour
  });

Without explicit content-type, Supabase may incorrectly detect file types, especially for binary files.

Concurrent Uploads to Same Path: If multiple processes try to upload to the same path simultaneously, race conditions may occur even with upsert enabled. For atomic file operations, consider using unique temporary names and renaming afterward, or using transactions at the application level.

Frequently asked questions

What does "409" mean?
The "Resource already exists" error (HTTP 409 Conflict) is thrown by Supabase Storage API when you attempt to upload a file to a location where a file already exists. Unlike some systems that overwrite files by default, Supabase's storage layer is designed to prevent accidental overwrites, making this an intentional safety mechanism. This error typically occurs during file upload operations and indicates that the specified file path is already occupied.
How do I fix "409"?
This error occurs when attempting to upload a file to Supabase Storage at a path where a file already exists. By default, Supabase rejects file uploads that would overwrite existing resources. The solution is to enable the upsert option to allow file replacement or delete the existing file first.
What causes "409"?
Uploading a file to the same path without using the upsert option Missing upsert: true flag in file upload options Attempting to create a file at a path where another version exists
Is the "409" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading