Storage
Supabase Storage
Setting up Supabase Storage as your storage provider.
Setup
- Create a Supabase project at supabase.com
- Go to Storage and create a bucket (e.g.
avatars) - Set the bucket to public if you want direct public URLs
- Set environment variables:
NEXT_PUBLIC_STORAGE_SERVICE="supabase"
NEXT_PUBLIC_SUPABASE_URL="https://xxx.supabase.co"
SUPABASE_SERVICE_ROLE_KEY="eyJ..."
SUPABASE_STORAGE_BUCKET="avatars"SUPABASE_STORAGE_BUCKET defaults to "avatars" if not set.
How it works
The Supabase provider uses the Supabase JS client with the service role key:
function createSupabaseProvider(): StorageProvider {
const bucket = process.env.SUPABASE_STORAGE_BUCKET || "avatars";
return {
async upload(file, key, contentType) {
const { createClient } = await import("@supabase/supabase-js");
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const { error } = await supabase.storage
.from(bucket)
.upload(key, file, { contentType, upsert: true });
if (error) throw new Error(`Supabase upload failed: ${error.message}`);
const { data: urlData } = supabase.storage
.from(bucket)
.getPublicUrl(key);
return { url: urlData.publicUrl, key };
},
async delete(key) {
// Similar setup, calls supabase.storage.from(bucket).remove([key])
},
};
}The upsert: true option overwrites existing files with the same key, which is used when replacing avatars.
URL format
Supabase Storage public URLs follow this pattern:
{SUPABASE_URL}/storage/v1/object/public/{bucket}/{key}For example: https://xxx.supabase.co/storage/v1/object/public/avatars/user-123.jpg
Important notes
- The service role key (
SUPABASE_SERVICE_ROLE_KEY) bypasses Row Level Security — never expose it on the client side - Make sure the bucket is set to public for direct URL access
- The Supabase JS client is dynamically imported to avoid bundling it when R2 is used