Learn-skills.dev supabase-storage
Supabase Storage for file uploads, downloads, buckets, and signed URLs. Use when uploading files, managing storage buckets, generating signed URLs, or handling images.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/adaptationio/skrillz/supabase-storage" ~/.claude/skills/neversight-learn-skills-dev-supabase-storage && rm -rf "$T"
manifest:
data/skills-md/adaptationio/skrillz/supabase-storage/SKILL.mdsource content
Supabase Storage Skill
File storage, uploads, downloads, and bucket management.
Quick Reference
| Task | Method |
|---|---|
| Upload file | |
| Download file | |
| Get public URL | |
| Get signed URL | |
| Delete file | |
| List files | |
| Move file | |
| Copy file | |
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