Bỏ qua

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

  1. Upload file size limit cần bao nhiêu?
  2. Có cần resumable upload không?
  3. Cần validate file types nào?