Supabase Storage lets you upload, download, and serve files like images, videos, and documents. Files are organized in buckets (similar to folders), and access is controlled by policies just like database tables.

Assumes Supabase is initialized. See Auth for setup.

Upload

First, create a bucket in the Supabase dashboard (Storage → New Bucket). Choose whether it is public (anyone can read) or private (requires auth). Then upload files from Flutter.

dart
import 'dart:io';

// Upload a file from disk
final file = File('/path/to/photo.jpg');
await supabase.storage
    .from('avatars')           // bucket name
    .upload('user123/avatar.jpg', file);  // path inside the bucket

// Upload from raw bytes (useful with image_picker)
await supabase.storage
    .from('avatars')
    .uploadBinary('user123/avatar.jpg', fileBytes);

// Overwrite an existing file (upsert)
await supabase.storage.from('avatars').upload(
  'user123/avatar.jpg',
  file,
  fileOptions: FileOptions(upsert: true),
);

Download

Download a file as raw bytes. You can then display it as an image or save it to the device.

dart
final bytes = await supabase.storage
    .from('avatars')
    .download('user123/avatar.jpg');

// Display as an image
Image.memory(bytes)

Public URL

For public buckets, get a permanent URL that anyone can access without authentication. This is the simplest way to display images.

dart
final url = supabase.storage
    .from('avatars')
    .getPublicUrl('user123/avatar.jpg');

// Use directly in a widget
Image.network(url)

Signed URL

For private buckets, generate a temporary URL that expires after a set number of seconds. Only people with the signed URL can access the file, and it stops working after expiration.

dart
// Generate a URL that expires in 1 hour (3600 seconds)
final url = await supabase.storage
    .from('documents')
    .createSignedUrl('user123/contract.pdf', 3600);

Image Transforms

Resize and optimize images on the fly by adding transform options to the URL. ResizeMode.cover crops to fill the dimensions. ResizeMode.contain fits within the dimensions without cropping. ResizeMode.fill stretches to fill exactly.

dart
final url = supabase.storage
    .from('avatars')
    .getPublicUrl(
      'user123/avatar.jpg',
      transform: TransformOptions(
        width: 200,
        height: 200,
        resize: ResizeMode.cover,
      ),
    );

Delete

Remove files from a bucket. Pass an array of file paths, even if you are only deleting one.

dart
// Delete a single file
await supabase.storage
    .from('avatars')
    .remove(['user123/avatar.jpg']);

// Delete multiple files
await supabase.storage
    .from('avatars')
    .remove([
      'user123/photo1.jpg',
      'user123/photo2.jpg',
    ]);

List Files

List all files in a folder within a bucket.

dart
final files = await supabase.storage
    .from('avatars')
    .list(path: 'user123');

for (final file in files) {
  print(file.name); // avatar.jpg
}

Access Policies

Storage uses the same RLS policy system as the database. Create policies in the Supabase dashboard under Storage → Policies. The pattern below restricts users to only access files in their own folder (named after their user ID).

sql
-- Users can upload files to their own folder
CREATE POLICY "Users upload own files"
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars'
  AND auth.uid()::text = (storage.foldername(name))[1]
);

-- Anyone can view files in the public bucket
CREATE POLICY "Public read"
ON storage.objects FOR SELECT
USING (bucket_id = 'avatars');