Agent Skills: Supabase Storage Skill

Supabase Storage for file uploads, downloads, buckets, and signed URLs. Use when uploading files, managing storage buckets, generating signed URLs, or handling images.

UncategorizedID: adaptationio/skrillz/supabase-storage

Install this agent skill to your local

pnpm dlx add-skill https://github.com/adaptationio/Skrillz/tree/HEAD/.claude/skills/supabase-storage

Skill Files

Browse the full folder contents for supabase-storage.

Download Skill

Loading file tree…

.claude/skills/supabase-storage/SKILL.md

Skill Metadata

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)

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