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.
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.
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.
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.
// 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.
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.
// 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.
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).
-- 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');