supabase-storage
SKILL.md
Supabase Storage Skill
File storage, uploads, downloads, and bucket management.
Quick Reference
| Task | Method |
|---|---|
| Upload file | storage.from('bucket').upload(path, file) |
| Download file | storage.from('bucket').download(path) |
| Get public URL | storage.from('bucket').getPublicUrl(path) |
| Get signed URL | storage.from('bucket').createSignedUrl(path, 3600) |
| Delete file | storage.from('bucket').remove([path]) |
| List files | storage.from('bucket').list(folder) |
| Move file | storage.from('bucket').move(from, to) |
| Copy file | storage.from('bucket').copy(from, to) |
Bucket Configuration
Public vs Private
- Public: Files accessible via URL without authentication
- Private: Requires authentication or signed URLs
Create Bucket (Dashboard)
- Go to Storage in Dashboard
- Click "New bucket"
- Set name and public/private
- Configure file size limits and allowed MIME types
Create Bucket (SQL)
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'avatars',
'avatars',
true,
5242880, -- 5MB
ARRAY['image/jpeg', 'image/png', 'image/webp']
);
Create Bucket (config.toml)
[storage.buckets.avatars]
public = true
file_size_limit = "5MiB"
allowed_mime_types = ["image/png", "image/jpeg", "image/webp"]
[storage.buckets.documents]
public = false
file_size_limit = "50MiB"
allowed_mime_types = ["application/pdf"]
Upload Files
Basic Upload
const { data, error } = await supabase.storage
.from('avatars')
.upload('user-123/avatar.png', file)
Upload with Options
const { data, error } = await supabase.storage
.from('avatars')
.upload('user-123/avatar.png', file, {
cacheControl: '3600',
contentType: 'image/png',
upsert: true // Replace if exists
})
Upload from Browser
const fileInput = document.querySelector('input[type="file"]')
const file = fileInput.files[0]
const { data, error } = await supabase.storage
.from('uploads')
.upload(`${userId}/${file.name}`, file)
Upload Base64
const base64Data = 'data:image/png;base64,iVBOR...'
const base64 = base64Data.split(',')[1]
const buffer = Uint8Array.from(atob(base64), c => c.charCodeAt(0))
const { data, error } = await supabase.storage
.from('images')
.upload('photo.png', buffer, {
contentType: 'image/png'
})
Download Files
Download as Blob
const { data, error } = await supabase.storage
.from('documents')
.download('report.pdf')
// data is a Blob
const url = URL.createObjectURL(data)
Download to Browser
const { data } = await supabase.storage
.from('documents')
.download('report.pdf')
const link = document.createElement('a')
link.href = URL.createObjectURL(data)
link.download = 'report.pdf'
link.click()
Get URLs
Public URL (Public Buckets)
const { data } = supabase.storage
.from('avatars')
.getPublicUrl('user-123/avatar.png')
console.log(data.publicUrl)
// https://xxx.supabase.co/storage/v1/object/public/avatars/user-123/avatar.png
Signed URL (Private Buckets)
const { data, error } = await supabase.storage
.from('documents')
.createSignedUrl('private/report.pdf', 3600) // 1 hour
console.log(data.signedUrl)
Multiple Signed URLs
const { data, error } = await supabase.storage
.from('documents')
.createSignedUrls(['doc1.pdf', 'doc2.pdf'], 3600)
Signed Upload URL
const { data, error } = await supabase.storage
.from('uploads')
.createSignedUploadUrl('user-123/file.pdf')
// data.signedUrl is valid for 2 hours
// data.token is the upload token
List Files
List All in Folder
const { data, error } = await supabase.storage
.from('uploads')
.list('user-123')
// data: [{ name, id, metadata, ... }]
With Options
const { data, error } = await supabase.storage
.from('uploads')
.list('user-123', {
limit: 100,
offset: 0,
sortBy: { column: 'created_at', order: 'desc' }
})
Search Files
const { data, error } = await supabase.storage
.from('uploads')
.list('user-123', {
search: 'report' // Filename contains 'report'
})
Delete Files
Single File
const { data, error } = await supabase.storage
.from('uploads')
.remove(['user-123/old-file.pdf'])
Multiple Files
const { data, error } = await supabase.storage
.from('uploads')
.remove([
'user-123/file1.pdf',
'user-123/file2.pdf',
'user-123/file3.pdf'
])
Move & Copy
Move File
const { data, error } = await supabase.storage
.from('uploads')
.move('old-path/file.pdf', 'new-path/file.pdf')
Copy File
const { data, error } = await supabase.storage
.from('uploads')
.copy('original/file.pdf', 'backup/file.pdf')
Image Transformations
Available on Pro plan and above.
Resize
const { data } = supabase.storage
.from('avatars')
.getPublicUrl('user-123/photo.jpg', {
transform: {
width: 200,
height: 200,
resize: 'cover' // cover, contain, fill
}
})
Quality
const { data } = supabase.storage
.from('images')
.getPublicUrl('photo.jpg', {
transform: {
width: 800,
quality: 75 // 20-100
}
})
Format Conversion
const { data } = supabase.storage
.from('images')
.getPublicUrl('photo.png', {
transform: {
format: 'webp' // webp, jpeg, png
}
})
Storage RLS Policies
Enable RLS
-- RLS is enabled by default on storage.objects
Common Policies
-- Users can view their own files
CREATE POLICY "Users can view own files"
ON storage.objects FOR SELECT
TO authenticated
USING (bucket_id = 'uploads' AND auth.uid()::text = (storage.foldername(name))[1]);
-- Users can upload to their folder
CREATE POLICY "Users can upload own files"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'uploads' AND auth.uid()::text = (storage.foldername(name))[1]);
-- Users can delete their files
CREATE POLICY "Users can delete own files"
ON storage.objects FOR DELETE
TO authenticated
USING (bucket_id = 'uploads' AND auth.uid()::text = (storage.foldername(name))[1]);
Public Bucket Policy
-- Anyone can view public files
CREATE POLICY "Public read"
ON storage.objects FOR SELECT
TO public
USING (bucket_id = 'public-images');
Error Handling
const { data, error } = await supabase.storage
.from('uploads')
.upload('file.pdf', file)
if (error) {
if (error.message === 'The resource already exists') {
console.log('File already exists')
} else if (error.message.includes('exceeded')) {
console.log('File too large')
} else if (error.message.includes('mime type')) {
console.log('Invalid file type')
} else {
console.error('Upload error:', error.message)
}
}
Size Limits
| Plan | Max File Size |
|---|---|
| Free | 50 MB |
| Pro+ | 500 GB |
References
- storage-policies.md - RLS policy patterns
- upload-patterns.md - Upload best practices