--- title: "File Uploads" description: "Handle file uploads with Supabase Storage." publishedAt: 2024-04-11 order: 2 status: "published" --- > **Note:** This is mock/placeholder content for demonstration purposes. Enable users to upload and manage files using Supabase Storage. ## Setup ### Create Storage Bucket ```sql -- Create a public bucket for avatars INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true); -- Create a private bucket for documents INSERT INTO storage.buckets (id, name, public) VALUES ('documents', 'documents', false); ``` ### Set Storage Policies ```sql -- Allow users to upload their own avatars CREATE POLICY "Users can upload their own avatar" ON storage.objects FOR INSERT WITH CHECK ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] ); -- Allow users to view their own avatars CREATE POLICY "Users can view their own avatar" ON storage.objects FOR SELECT USING ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] ); -- Allow users to delete their own avatars CREATE POLICY "Users can delete their own avatar" ON storage.objects FOR DELETE USING ( bucket_id = 'avatars' AND auth.uid()::text = (storage.foldername(name))[1] ); ``` ## Upload Component ### Basic File Upload ```tsx 'use client'; import { useState } from 'react'; import { uploadFileAction } from '../_lib/actions'; export function FileUpload() { const [uploading, setUploading] = useState(false); const [file, setFile] = useState(null); const handleUpload = async () => { if (!file) return; setUploading(true); const formData = new FormData(); formData.append('file', file); const result = await uploadFileAction(formData); if (result.success) { toast.success('File uploaded successfully'); } setUploading(false); }; return (
setFile(e.files?.[0] || null)} accept="image/*" />
); } ``` ### Server Action ```typescript 'use server'; import { enhanceAction } from '@kit/next/actions'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; export const uploadFileAction = enhanceAction( async (formData: FormData, user) => { const file = formData.get('file') as File; if (!file) { throw new Error('No file provided'); } const client = getSupabaseServerClient(); const fileExt = file.name.split('.').pop(); const fileName = `${user.id}/${Date.now()}.${fileExt}`; const { data, error } = await client.storage .from('avatars') .upload(fileName, file, { cacheControl: '3600', upsert: false, }); if (error) throw error; // Get public URL const { data: { publicUrl } } = client.storage .from('avatars') .getPublicUrl(fileName); return { success: true, url: publicUrl, path: data.path, }; }, { auth: true } ); ``` ## Drag and Drop Upload ```tsx 'use client'; import { useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; export function DragDropUpload() { const onDrop = useCallback(async (acceptedFiles: File[]) => { for (const file of acceptedFiles) { const formData = new FormData(); formData.append('file', file); await uploadFileAction(formData); } }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop, accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif'], }, maxSize: 5 * 1024 * 1024, // 5MB }); return (
{isDragActive ? (

Drop files here...

) : (

Drag and drop files here, or click to select

)}
); } ``` ## File Validation ### Client-Side Validation ```typescript function validateFile(file: File) { const maxSize = 5 * 1024 * 1024; // 5MB const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (file.size > maxSize) { throw new Error('File size must be less than 5MB'); } if (!allowedTypes.includes(file.type)) { throw new Error('File type must be JPEG, PNG, or GIF'); } return true; } ``` ### Server-Side Validation ```typescript export const uploadFileAction = enhanceAction( async (formData: FormData, user) => { const file = formData.get('file') as File; // Validate file size if (file.size > 5 * 1024 * 1024) { throw new Error('File too large'); } // Validate file type const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!allowedTypes.includes(file.type)) { throw new Error('Invalid file type'); } // Validate dimensions for images if (file.type.startsWith('image/')) { const dimensions = await getImageDimensions(file); if (dimensions.width > 4000 || dimensions.height > 4000) { throw new Error('Image dimensions too large'); } } // Continue with upload... }, { auth: true } ); ``` ## Image Optimization ### Resize on Upload ```typescript import sharp from 'sharp'; export const uploadAvatarAction = enhanceAction( async (formData: FormData, user) => { const file = formData.get('file') as File; const buffer = Buffer.from(await file.arrayBuffer()); // Resize image const resized = await sharp(buffer) .resize(200, 200, { fit: 'cover', position: 'center', }) .jpeg({ quality: 90 }) .toBuffer(); const client = getSupabaseServerClient(); const fileName = `${user.id}/avatar.jpg`; const { error } = await client.storage .from('avatars') .upload(fileName, resized, { contentType: 'image/jpeg', upsert: true, }); if (error) throw error; return { success: true }; }, { auth: true } ); ``` ## Progress Tracking ```tsx 'use client'; import { useState } from 'react'; export function UploadWithProgress() { const [progress, setProgress] = useState(0); const handleUpload = async (file: File) => { const client = getSupabaseBrowserClient(); const { error } = await client.storage .from('documents') .upload(`uploads/${file.name}`, file, { onUploadProgress: (progressEvent) => { const percent = (progressEvent.loaded / progressEvent.total) * 100; setProgress(Math.round(percent)); }, }); if (error) throw error; }; return (
handleUpload(e.target.files![0])} /> {progress > 0 && (
)}
); } ``` ## Downloading Files ### Get Public URL ```typescript const { data } = client.storage .from('avatars') .getPublicUrl('user-id/avatar.jpg'); console.log(data.publicUrl); ``` ### Download Private File ```typescript const { data, error } = await client.storage .from('documents') .download('private-file.pdf'); if (data) { const url = URL.createObjectURL(data); const a = document.createElement('a'); a.href = url; a.download = 'file.pdf'; a.click(); } ``` ### Generate Signed URL ```typescript const { data, error } = await client.storage .from('documents') .createSignedUrl('private-file.pdf', 3600); // 1 hour console.log(data.signedUrl); ``` ## Deleting Files ```typescript export const deleteFileAction = enhanceAction( async (data, user) => { const client = getSupabaseServerClient(); const { error } = await client.storage .from('avatars') .remove([data.path]); if (error) throw error; return { success: true }; }, { schema: z.object({ path: z.string(), }), auth: true, } ); ``` ## Best Practices 1. **Validate on both sides** - Client and server 2. **Limit file sizes** - Prevent abuse 3. **Sanitize filenames** - Remove special characters 4. **Use unique names** - Prevent collisions 5. **Optimize images** - Resize before upload 6. **Set storage policies** - Control access 7. **Monitor usage** - Track storage costs 8. **Clean up unused files** - Regular maintenance 9. **Use CDN** - For public files 10. **Implement virus scanning** - For user uploads