Supabase Storage for file uploads, downloads, buckets, and signed URLs. Use when uploading files, managing storage buckets, generating signed URLs, or handling images.
Scanned 2/10/2026
Install via CLI
openskills install adaptationio/Skrillz---
name: supabase-storage
description: Supabase Storage for file uploads, downloads, buckets, and signed URLs. Use when uploading files, managing storage buckets, generating signed URLs, or handling images.
---
# 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)
1. Go to Storage in Dashboard
2. Click "New bucket"
3. Set name and public/private
4. Configure file size limits and allowed MIME types
### Create Bucket (SQL)
```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)
```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
```javascript
const { data, error } = await supabase.storage
.from('avatars')
.upload('user-123/avatar.png', file)
```
### Upload with Options
```javascript
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
```javascript
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
```javascript
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
```javascript
const { data, error } = await supabase.storage
.from('documents')
.download('report.pdf')
// data is a Blob
const url = URL.createObjectURL(data)
```
### Download to Browser
```javascript
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)
```javascript
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)
```javascript
const { data, error } = await supabase.storage
.from('documents')
.createSignedUrl('private/report.pdf', 3600) // 1 hour
console.log(data.signedUrl)
```
### Multiple Signed URLs
```javascript
const { data, error } = await supabase.storage
.from('documents')
.createSignedUrls(['doc1.pdf', 'doc2.pdf'], 3600)
```
### Signed Upload URL
```javascript
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
```javascript
const { data, error } = await supabase.storage
.from('uploads')
.list('user-123')
// data: [{ name, id, metadata, ... }]
```
### With Options
```javascript
const { data, error } = await supabase.storage
.from('uploads')
.list('user-123', {
limit: 100,
offset: 0,
sortBy: { column: 'created_at', order: 'desc' }
})
```
### Search Files
```javascript
const { data, error } = await supabase.storage
.from('uploads')
.list('user-123', {
search: 'report' // Filename contains 'report'
})
```
## Delete Files
### Single File
```javascript
const { data, error } = await supabase.storage
.from('uploads')
.remove(['user-123/old-file.pdf'])
```
### Multiple Files
```javascript
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
```javascript
const { data, error } = await supabase.storage
.from('uploads')
.move('old-path/file.pdf', 'new-path/file.pdf')
```
### Copy File
```javascript
const { data, error } = await supabase.storage
.from('uploads')
.copy('original/file.pdf', 'backup/file.pdf')
```
## Image Transformations
Available on Pro plan and above.
### Resize
```javascript
const { data } = supabase.storage
.from('avatars')
.getPublicUrl('user-123/photo.jpg', {
transform: {
width: 200,
height: 200,
resize: 'cover' // cover, contain, fill
}
})
```
### Quality
```javascript
const { data } = supabase.storage
.from('images')
.getPublicUrl('photo.jpg', {
transform: {
width: 800,
quality: 75 // 20-100
}
})
```
### Format Conversion
```javascript
const { data } = supabase.storage
.from('images')
.getPublicUrl('photo.png', {
transform: {
format: 'webp' // webp, jpeg, png
}
})
```
## Storage RLS Policies
### Enable RLS
```sql
-- RLS is enabled by default on storage.objects
```
### Common Policies
```sql
-- 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
```sql
-- Anyone can view public files
CREATE POLICY "Public read"
ON storage.objects FOR SELECT
TO public
USING (bucket_id = 'public-images');
```
## Error Handling
```javascript
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](references/storage-policies.md) - RLS policy patterns
- [upload-patterns.md](references/upload-patterns.md) - Upload best practices
No comments yet. Be the first to comment!