Supabase Storage for file uploads, downloads, buckets, and signed URLs. Use when uploading files, managing storage buckets, generating signed URLs, or handling images.
Scanned 9/4/2026
Install to Claude Code
npx -y skills add NeverSight/skills_feed --skill supabase-storage --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Supabase Storage?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/neversight-supabase-storage)More formats (shields.io, HTML) on the badges page.
---
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
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!