File Operations
Upload Files
Basic Upload
// Upload file từ form input
const file = event.target.files[0];
const { data, error } = await supabase.storage
.from('avatars')
.upload(`${userId}/avatar.jpg`, file);
if (error) {
console.error('Upload failed:', error.message);
} else {
console.log('Uploaded:', data.path);
}
Upload với Options
const { data, error } = await supabase.storage
.from('documents')
.upload('reports/annual-2024.pdf', file, {
// Cache control
cacheControl: '3600',
// Upsert: replace if exists
upsert: true,
// Custom content type
contentType: 'application/pdf',
// Duplex for streaming (Node.js)
duplex: 'half',
});
Upload từ Base64
// Convert base64 to file
const base64Data = 'data:image/png;base64,iVBORw0KGgo...';
// Remove data URL prefix
const base64 = base64Data.split(',')[1];
// Decode base64
const { data, error } = await supabase.storage
.from('images')
.upload('image.png', decode(base64), {
contentType: 'image/png',
});
Upload từ URL (Server-side)
// Fetch remote file and upload
const response = await fetch('https://example.com/image.jpg');
const blob = await response.blob();
const { data, error } = await supabase.storage
.from('imports')
.upload('imported-image.jpg', blob);
Download Files
Download to Memory
// Download file content
const { data, error } = await supabase.storage
.from('documents')
.download('reports/annual.pdf');
if (data) {
// data is a Blob
const url = URL.createObjectURL(data);
// Use url for display or download link
}
Get Public URL
// For public buckets
const { data } = supabase.storage
.from('public-images')
.getPublicUrl('hero.jpg');
console.log(data.publicUrl);
// https://xxx.supabase.co/storage/v1/object/public/public-images/hero.jpg
Create Signed URL
// For private buckets - time-limited access
const { data, error } = await supabase.storage
.from('private-docs')
.createSignedUrl('contract.pdf', 3600); // 1 hour
if (data) {
console.log(data.signedUrl);
// URL expires after 3600 seconds
}
Multiple Signed URLs
// Batch create signed URLs
const { data, error } = await supabase.storage
.from('documents')
.createSignedUrls(
['doc1.pdf', 'doc2.pdf', 'doc3.pdf'],
3600
);
// data = [{ path, signedUrl }, ...]
List Files
List Files in Folder
// List files in bucket root
const { data, error } = await supabase.storage
.from('documents')
.list();
// List files in folder
const { data: files } = await supabase.storage
.from('documents')
.list('reports/2024', {
limit: 100,
offset: 0,
sortBy: { column: 'name', order: 'asc' },
});
// data = [{ name, id, metadata, created_at, ... }, ...]
Search Files
// Search with prefix
const { data } = await supabase.storage
.from('documents')
.list('', {
search: 'report', // Files starting with 'report'
});
Move & Copy Files
Move File
// Move file to different location
const { data, error } = await supabase.storage
.from('documents')
.move('old-folder/file.pdf', 'new-folder/file.pdf');
Copy File
// Copy file
const { data, error } = await supabase.storage
.from('documents')
.copy('original/file.pdf', 'backup/file.pdf');
Delete Files
Delete Single File
const { error } = await supabase.storage
.from('documents')
.remove(['reports/old-report.pdf']);
Delete Multiple Files
const { data, error } = await supabase.storage
.from('documents')
.remove([
'temp/file1.pdf',
'temp/file2.pdf',
'temp/file3.pdf',
]);
Delete Folder
// List all files in folder first
const { data: files } = await supabase.storage
.from('documents')
.list('temp-folder');
// Then delete all
if (files && files.length > 0) {
const filePaths = files.map(f => `temp-folder/${f.name}`);
await supabase.storage.from('documents').remove(filePaths);
}
React Upload Component
Basic File Upload
'use client';
import { useState } from 'react';
import { createClient } from '@/lib/supabase/client';
export function FileUpload({ bucket, folder }: {
bucket: string;
folder: string;
}) {
const [uploading, setUploading] = useState(false);
const [uploadedUrl, setUploadedUrl] = useState<string | null>(null);
const supabase = createClient();
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
// Generate unique filename
const fileExt = file.name.split('.').pop();
const fileName = `${Date.now()}.${fileExt}`;
const filePath = `${folder}/${fileName}`;
const { data, error } = await supabase.storage
.from(bucket)
.upload(filePath, file);
if (error) {
console.error('Upload error:', error);
} else {
// Get URL
const { data: urlData } = supabase.storage
.from(bucket)
.getPublicUrl(data.path);
setUploadedUrl(urlData.publicUrl);
}
setUploading(false);
};
return (
<div>
<input
type="file"
onChange={handleUpload}
disabled={uploading}
/>
{uploading && <p>Uploading...</p>}
{uploadedUrl && <img src={uploadedUrl} alt="Uploaded" />}
</div>
);
}
Avatar Upload with Preview
'use client';
import { useState, useRef } from 'react';
import { createClient } from '@/lib/supabase/client';
export function AvatarUpload({ userId, currentUrl }: {
userId: string;
currentUrl?: string;
}) {
const [preview, setPreview] = useState<string | null>(currentUrl || null);
const [uploading, setUploading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const supabase = createClient();
const handleFileSelect = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
// Validate file
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
if (file.size > 5 * 1024 * 1024) {
alert('File size must be less than 5MB');
return;
}
// Show preview
const reader = new FileReader();
reader.onload = (e) => setPreview(e.target?.result as string);
reader.readAsDataURL(file);
// Upload
setUploading(true);
const filePath = `${userId}/avatar.jpg`;
const { error } = await supabase.storage
.from('avatars')
.upload(filePath, file, { upsert: true });
if (error) {
console.error('Upload failed:', error);
setPreview(currentUrl || null);
}
setUploading(false);
};
return (
<div onClick={() => inputRef.current?.click()}>
{preview ? (
<img src={preview} alt="Avatar" className="avatar" />
) : (
<div className="avatar-placeholder">Upload</div>
)}
<input
ref={inputRef}
type="file"
accept="image/*"
onChange={handleFileSelect}
hidden
/>
{uploading && <span>Uploading...</span>}
</div>
);
}
Resumable Uploads
Large File Upload
import { createClient } from '@supabase/supabase-js';
// For files > 6MB, use resumable upload
const supabase = createClient(url, key);
// Start upload
const { data, error } = await supabase.storage
.from('large-files')
.upload('video.mp4', largeFile, {
// Resumable upload for large files
upsert: true,
});
// Supabase automatically handles chunking for files > 6MB
TUS Protocol (Advanced)
import * as tus from 'tus-js-client';
const upload = new tus.Upload(file, {
endpoint: `${SUPABASE_URL}/storage/v1/upload/resumable`,
headers: {
authorization: `Bearer ${session.access_token}`,
'x-upsert': 'true',
},
uploadDataDuringCreation: true,
metadata: {
bucketName: 'large-files',
objectName: 'video.mp4',
contentType: 'video/mp4',
},
chunkSize: 6 * 1024 * 1024, // 6MB chunks
onError: (error) => console.error('Upload error:', error),
onProgress: (bytesUploaded, bytesTotal) => {
const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
console.log(`Progress: ${percentage}%`);
},
onSuccess: () => console.log('Upload complete!'),
});
upload.start();
Tổng kết
File Operations Summary
| Operation |
Method |
| Upload |
.upload(path, file) |
| Download |
.download(path) |
| Get public URL |
.getPublicUrl(path) |
| Get signed URL |
.createSignedUrl(path, expiry) |
| List files |
.list(folder) |
| Move |
.move(from, to) |
| Copy |
.copy(from, to) |
| Delete |
.remove([paths]) |
Best Practices
✅ Validate file type and size before upload
✅ Generate unique filenames (prevent overwrite)
✅ Use user ID in path for organization
✅ Handle upload errors gracefully
✅ Show upload progress for large files
✅ Clean up unused files periodically
Q&A
- Upload file size limit cần bao nhiêu?
- Có cần resumable upload không?
- Cần validate file types nào?