Storage Concepts
Supabase Storage là gì?
Overview
┌─────────────────────────────────────────────────────────────┐
│ SUPABASE STORAGE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Storage │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Bucket │ │ Bucket │ │ Bucket │ │ │
│ │ │ avatars │ │ docs │ │ images │ │ │
│ │ ├─────────┤ ├─────────┤ ├─────────┤ │ │
│ │ │file1.jpg│ │doc1.pdf │ │img1.png │ │ │
│ │ │file2.jpg│ │doc2.pdf │ │img2.png │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ Features: │
│ - S3-compatible object storage │
│ - RLS policies for access control │
│ - Image transformations │
│ - CDN integration │
│ - Resumable uploads │
│ │
└─────────────────────────────────────────────────────────────┘
Core Concepts
Buckets
Bucket = Container for files (như folder level 1)
Types:
├── Public Bucket
│ - Files accessible via public URL
│ - No auth required to download
│ - Good for: logos, public images
│
└── Private Bucket
- Files require authentication
- Signed URLs for access
- Good for: user uploads, documents
File Paths
storage/
└── bucket-name/
└── folder/
└── subfolder/
└── file.jpg
Full path: bucket-name/folder/subfolder/file.jpg
Create Buckets
Via Dashboard
Supabase Dashboard → Storage → New bucket
Settings:
- Name: avatars
- Public bucket: Yes/No
- File size limit: 50MB (optional)
- Allowed MIME types: image/* (optional)
Via SQL
-- Create public bucket
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);
-- Create private bucket
INSERT INTO storage.buckets (id, name, public, file_size_limit)
VALUES ('documents', 'documents', false, 10485760); -- 10MB limit
Via Client
const { data, error } = await supabase.storage.createBucket('avatars', {
public: true,
fileSizeLimit: 1024 * 1024 * 5, // 5MB
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
});
Storage RLS Policies
Policy Structure
-- Storage policies on storage.objects table
CREATE POLICY "policy_name"
ON storage.objects
FOR operation -- SELECT, INSERT, UPDATE, DELETE
USING (condition)
WITH CHECK (condition);
Common Policies
-- Anyone can view public bucket files
CREATE POLICY "Public read access"
ON storage.objects FOR SELECT
USING (bucket_id = 'public-images');
-- Authenticated users can upload to avatars
CREATE POLICY "Auth users upload avatars"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'avatars'
AND auth.role() = 'authenticated'
);
-- Users can only access own files
CREATE POLICY "Users access own files"
ON storage.objects FOR ALL
USING (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()::text
);
File Path Patterns
User-specific Folders
// Pattern: bucket/user_id/filename
const userId = user.id;
const filePath = `${userId}/profile.jpg`;
await supabase.storage
.from('avatars')
.upload(filePath, file);
// URL: .../avatars/user-uuid/profile.jpg
Project-based Organization
// Pattern: bucket/project_id/filename
const projectId = 'project-uuid';
const filePath = `${projectId}/documents/report.pdf`;
await supabase.storage
.from('project-files')
.upload(filePath, file);
RLS for Path-based Access
-- Policy using folder name
CREATE POLICY "Users access own folder"
ON storage.objects FOR ALL
USING (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()::text
)
WITH CHECK (
bucket_id = 'user-files'
AND (storage.foldername(name))[1] = auth.uid()::text
);
Storage Functions
Built-in Functions
-- Get folder names from path
storage.foldername('folder/subfolder/file.jpg')
-- Returns: ['folder', 'subfolder']
-- Get filename
storage.filename('folder/subfolder/file.jpg')
-- Returns: 'file.jpg'
-- Get file extension
storage.extension('folder/file.jpg')
-- Returns: 'jpg'
Usage in Policies
-- Only allow image files
CREATE POLICY "Only images allowed"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'images'
AND storage.extension(name) IN ('jpg', 'jpeg', 'png', 'gif', 'webp')
);
-- Limit to specific folder depth
CREATE POLICY "Max 2 folder depth"
ON storage.objects FOR INSERT
WITH CHECK (
array_length(storage.foldername(name), 1) <= 2
);
Public vs Private Access
Public Bucket
// Public bucket - direct URL access
const { data } = supabase.storage
.from('public-images')
.getPublicUrl('image.jpg');
// data.publicUrl = https://xxx.supabase.co/storage/v1/object/public/public-images/image.jpg
// Anyone can access this URL
Private Bucket
// Private bucket - signed URL required
const { data, error } = await supabase.storage
.from('private-docs')
.createSignedUrl('document.pdf', 3600); // 1 hour expiry
// data.signedUrl = https://xxx.supabase.co/storage/v1/object/sign/private-docs/document.pdf?token=xxx
// URL expires after specified time
Storage Limits
Default Limits
┌─────────────────────────────────────────────────────────────┐
│ STORAGE LIMITS │
├─────────────────────────────────────────────────────────────┤
│ │
│ Free Tier: │
│ - 1 GB storage │
│ - 50 MB file size limit │
│ - 2 GB bandwidth/month │
│ │
│ Pro Tier: │
│ - 100 GB included │
│ - 5 GB file size limit │
│ - 250 GB bandwidth/month │
│ - $0.021/GB additional storage │
│ │
│ Custom limits per bucket: │
│ - file_size_limit │
│ - allowed_mime_types │
│ │
└─────────────────────────────────────────────────────────────┘
Best Practices
Naming Conventions
// ✅ Good: Descriptive, organized
'avatars' // User profile images
'project-files' // Project attachments
'public-assets' // Public static files
// File paths
`${userId}/profile.jpg`
`${projectId}/attachments/${fileId}-${originalName}`
// ❌ Bad: Generic, unorganized
'files'
'uploads'
Security Checklist
✅ Enable RLS on storage.objects
✅ Create specific policies per bucket
✅ Validate file types
✅ Limit file sizes
✅ Use signed URLs for private files
✅ Organize files by user/project ID
Tổng kết
Key Concepts
| Concept |
Description |
| Bucket |
Container for files |
| Public |
Direct URL access |
| Private |
Requires signed URL |
| RLS |
Access control via policies |
Storage Architecture
storage.buckets → Bucket definitions
storage.objects → File metadata (RLS here)
Actual files → S3-compatible storage
Q&A
- Cần lưu trữ loại file nào?
- Public hay private buckets?
- Đã dùng cloud storage trước đây chưa?