Claude
Skills
Sign in
Back

supabase-storage

Included with Lifetime
$97 forever

Expert guide for Supabase Storage including bucket management, file operations, URL generation, and RLS policies. Use when working with file uploads/downloads, creating public or private buckets, generating signed URLs, implementing storage RLS policies, handling resumable uploads, image transformations, or any Supabase Storage-related tasks.

Image & Video

What this skill does


# Supabase Storage Expert

You are an expert in Supabase Storage, specializing in file management, bucket configuration, access control, and Row Level Security (RLS) policies for storage objects.

## 1. Storage Architecture Overview

Supabase Storage provides S3-compatible object storage with built-in CDN, image transformations, and Row Level Security integration.

**Key Components:**
- **Buckets**: Containers for organizing files (public or private)
- **Objects**: Files stored within buckets
- **RLS Policies**: Security rules controlling access to buckets and objects
- **Helper Functions**: SQL functions for path manipulation and access control

---

## 2. Bucket Management

### Creating Buckets

#### JavaScript
```javascript
const { data, error } = await supabase.storage.createBucket('avatars', {
  public: true,
  allowedMimeTypes: ['image/*'],
  fileSizeLimit: '1MB',
})
```

#### SQL
```sql
insert into storage.buckets (id, name, public)
values ('avatars', 'avatars', true);
```

#### Config.toml (Local Development)
```toml
[storage.buckets.images]
public = false
file_size_limit = "50MiB"
allowed_mime_types = ["image/png", "image/jpeg"]
objects_path = "./images"
```

### Bucket Operations

**Get Bucket:**
```javascript
const { data, error } = await supabase.storage.getBucket('avatars')
```

**List Buckets:**
```javascript
const { data, error } = await supabase.storage.listBuckets()
```

**Update Bucket:**
```javascript
const { data, error } = await supabase.storage.updateBucket('avatars', {
  public: false,
  allowedMimeTypes: ['image/png', 'image/jpeg'],
  fileSizeLimit: '5MB'
})
```

**Delete Bucket:**
```javascript
const { data, error } = await supabase.storage.deleteBucket('avatars')
```

**Empty Bucket:**
```javascript
const { data, error } = await supabase.storage.emptyBucket('avatars')
```

---

## 3. File Operations

### Upload Files

#### Standard Upload
```javascript
const avatarFile = event.target.files[0]
const { data, error } = await supabase
  .storage
  .from('avatars')
  .upload('public/avatar1.png', avatarFile, {
    cacheControl: '3600',
    upsert: false
  })
```

#### Upload from Base64 (React Native)
```javascript
import { decode } from 'base64-arraybuffer'

const { data, error } = await supabase
  .storage
  .from('avatars')
  .upload('public/avatar1.png', decode('base64FileData'), {
    contentType: 'image/png'
  })
```

#### TypeScript Upload Function
```typescript
async function uploadAvatar(file: File, userId: string) {
  const fileExt = file.name.split('.').pop()
  const fileName = `${userId}-${Math.random()}.${fileExt}`
  const filePath = `avatars/${fileName}`

  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      cacheControl: '3600',
      upsert: false,
    })

  if (error) {
    throw new Error(`Upload failed: ${error.message}`)
  }

  // Get public URL
  const { data: urlData } = supabase.storage
    .from('avatars')
    .getPublicUrl(filePath)

  console.log('File uploaded to:', urlData.publicUrl)
  return { path: data.path, url: urlData.publicUrl }
}
```

### Resumable Uploads

#### Using tus-js-client
```javascript
const tus = require('tus-js-client')

async function uploadFile(bucketName, fileName, file) {
  const { data: { session } } = await supabase.auth.getSession()

  return new Promise((resolve, reject) => {
    var upload = new tus.Upload(file, {
      endpoint: `https://${projectId}.storage.supabase.co/storage/v1/upload/resumable`,
      retryDelays: [0, 3000, 5000, 10000, 20000],
      headers: {
        authorization: `Bearer ${session.access_token}`,
        'x-upsert': 'true'
      },
      uploadDataDuringCreation: true,
      removeFingerprintOnSuccess: true,
      metadata: {
        bucketName: bucketName,
        objectName: fileName,
        contentType: 'image/png',
        cacheControl: 3600
      },
      chunkSize: 6 * 1024 * 1024, // Must be 6MB
      onError: function (error) {
        reject(error)
      },
      onProgress: function (bytesUploaded, bytesTotal) {
        var percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2)
        console.log(percentage + '%')
      },
      onSuccess: function () {
        resolve()
      }
    })

    return upload.findPreviousUploads().then(function (previousUploads) {
      if (previousUploads.length) {
        upload.resumeFromPreviousUpload(previousUploads[0])
      }
      upload.start()
    })
  })
}
```

#### Using Uppy.js
```javascript
import { Uppy, Dashboard, Tus } from 'https://releases.transloadit.com/uppy/v3.6.1/uppy.min.mjs'

const token = 'anon-key'
const projectId = 'your-project-ref'
const bucketName = 'avatars'
const supabaseUploadURL = `https://${projectId}.supabase.co/storage/v1/upload/resumable`

var uppy = new Uppy()
  .use(Dashboard, {
    inline: true,
    target: '#drag-drop-area',
    showProgressDetails: true,
  })
  .use(Tus, {
    endpoint: supabaseUploadURL,
    headers: {
      authorization: `Bearer ${token}`,
    },
    chunkSize: 6 * 1024 * 1024,
    allowedMetaFields: ['bucketName', 'objectName', 'contentType', 'cacheControl'],
  })

uppy.on('file-added', (file) => {
  file.meta = {
    ...file.meta,
    bucketName: bucketName,
    objectName: `folder/${file.name}`,
    contentType: file.type,
  }
})

uppy.on('complete', (result) => {
  console.log('Upload complete!', result.successful)
})
```

### Download Files

```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .download('folder/avatar1.png')
```

**With Transformations:**
```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .download('folder/avatar1.png', {
    transform: {
      width: 100,
      height: 100,
      quality: 80
    }
  })
```

### List Files

```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .list('folder', {
    limit: 100,
    offset: 0,
    sortBy: { column: 'name', order: 'asc' }
  })
```

### Update/Replace File

```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .update('folder/avatar1.png', newFile, {
    cacheControl: '3600',
    upsert: true
  })
```

### Move File

```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .move('folder/avatar1.png', 'folder/avatar2.png')
```

### Copy File

```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .copy('folder/avatar1.png', 'folder/avatar1-copy.png')
```

### Remove Files

```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .remove(['folder/avatar1.png', 'folder/avatar2.png'])
```

---

## 4. URL Generation

### Public URLs

**For Public Buckets:**
```javascript
const { data } = supabase
  .storage
  .from('public-bucket')
  .getPublicUrl('folder/avatar1.png')

console.log(data.publicUrl)
```

**Direct URL Format:**
```
https://[project_id].supabase.co/storage/v1/object/public/[bucket]/[asset-name]
```

**Force Download:**
```
https://[project_id].supabase.co/storage/v1/object/public/[bucket]/[asset-name]?download
https://[project_id].supabase.co/storage/v1/object/public/[bucket]/[asset-name]?download=customname.jpg
```

**With Transformations:**
```javascript
const { data } = supabase
  .storage
  .from('public-bucket')
  .getPublicUrl('folder/avatar1.png', {
    transform: {
      width: 100,
      height: 100,
    }
  })
```

### Signed URLs (Private Buckets)

**Create Single Signed URL:**
```javascript
const { data, error } = await supabase
  .storage
  .from('avatars')
  .createSignedUrl('folder/avatar1.png', 60) // 60 seconds
```

**TypeScript Function:**
```typescript
async function createSignedUrl(filePath: string, expiresIn: number = 3600) {
  const { data, error } = await supabase.storage
    .from('private-files')
    .createSignedUrl(filePath, expiresIn)

  if (error) {
    throw new Error(`Signed URL creation failed: ${error.message}`)
  }

  console.log('Signed URL:', data.signedUrl)
  retu
Files: 1
Size: 17.1 KB
Complexity: 23/100
Category: Image & Video

Related in Image & Video