The Crystal platform needs file storage for:
- Profile Images - User profile pictures
- Company Logos - Brand logos
- Content Submissions - Images, videos, and media files from influencers
- Portfolio Items - Influencer portfolio media
- Message Attachments - Files shared in messages
- Thumbnails - Preview images for videos
- Go to your Supabase project dashboard
- Navigate to Storage in the left sidebar
You need to create the following buckets:
- Name:
profiles - Public: ✅ Yes (for profile images and logos)
- File size limit: 5 MB
- Allowed MIME types:
image/jpeg, image/png, image/webp
- Name:
submissions - Public: ❌ No (private, only accessible via signed URLs)
- File size limit: 100 MB (for videos)
- Allowed MIME types:
image/*, video/*
- Name:
portfolio - Public: ✅ Yes (for portfolio showcase)
- File size limit: 50 MB
- Allowed MIME types:
image/*, video/*
- Name:
attachments - Public: ❌ No (private messages)
- File size limit: 25 MB
- Allowed MIME types:
*/*(all file types)
For each bucket, you need to set up Row Level Security (RLS) policies:
-- Allow public read access
CREATE POLICY "Public profiles are viewable by everyone"
ON storage.objects FOR SELECT
USING (bucket_id = 'profiles');
-- Allow authenticated users to upload
CREATE POLICY "Users can upload their own profile images"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'profiles' AND
auth.role() = 'authenticated'
);
-- Allow users to update their own files
CREATE POLICY "Users can update their own profile images"
ON storage.objects FOR UPDATE
USING (
bucket_id = 'profiles' AND
auth.role() = 'authenticated'
);-- Allow users to upload submissions for their contracts
CREATE POLICY "Influencers can upload submissions"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'submissions' AND
auth.role() = 'authenticated'
);
-- Allow contract participants to view submissions
CREATE POLICY "Contract participants can view submissions"
ON storage.objects FOR SELECT
USING (
bucket_id = 'submissions' AND
auth.role() = 'authenticated'
);-- Public read access
CREATE POLICY "Portfolio items are viewable by everyone"
ON storage.objects FOR SELECT
USING (bucket_id = 'portfolio');
-- Authenticated users can upload
CREATE POLICY "Influencers can upload portfolio items"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'portfolio' AND
auth.role() = 'authenticated'
);-- Allow message participants to upload/view attachments
CREATE POLICY "Message participants can access attachments"
ON storage.objects FOR ALL
USING (
bucket_id = 'attachments' AND
auth.role() = 'authenticated'
);Add these to your .env.local:
# Supabase Storage
NEXT_PUBLIC_SUPABASE_STORAGE_URL=http://supabasekong-ekkowggsogww84gwgsowccso.31.97.34.56.sslip.io/storage/v1
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-hereNote: The service role key is different from the anon key and should be kept secret (server-side only).
Update next.config.js to allow Supabase Storage images:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: [
'localhost',
'res.cloudinary.com',
'supabasekong-ekkowggsogww84gwgsowccso.31.97.34.56.sslip.io'
],
},
}
module.exports = nextConfigYou can run this SQL in your Supabase SQL Editor to create all buckets and policies at once:
-- Create buckets (run these in Supabase Dashboard -> Storage -> New Bucket)
-- Or use Supabase CLI if available
-- Note: Buckets must be created via Dashboard or API, not SQL
-- But policies can be set via SQL:
-- Profiles bucket policies
CREATE POLICY IF NOT EXISTS "Public profiles are viewable by everyone"
ON storage.objects FOR SELECT
USING (bucket_id = 'profiles');
CREATE POLICY IF NOT EXISTS "Users can upload profile images"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'profiles' AND
auth.role() = 'authenticated'
);
-- Submissions bucket policies
CREATE POLICY IF NOT EXISTS "Influencers can upload submissions"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'submissions' AND
auth.role() = 'authenticated'
);
CREATE POLICY IF NOT EXISTS "Contract participants can view submissions"
ON storage.objects FOR SELECT
USING (
bucket_id = 'submissions' AND
auth.role() = 'authenticated'
);
-- Portfolio bucket policies
CREATE POLICY IF NOT EXISTS "Portfolio items are viewable by everyone"
ON storage.objects FOR SELECT
USING (bucket_id = 'portfolio');
CREATE POLICY IF NOT EXISTS "Influencers can upload portfolio items"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'portfolio' AND
auth.role() = 'authenticated'
);
-- Attachments bucket policies
CREATE POLICY IF NOT EXISTS "Message participants can access attachments"
ON storage.objects FOR ALL
USING (
bucket_id = 'attachments' AND
auth.role() = 'authenticated'
);After setup, test by uploading a file:
// Example: Upload profile image
const { data, error } = await supabase.storage
.from('profiles')
.upload(`${userId}/avatar.jpg`, file)
if (error) {
console.error('Upload error:', error)
} else {
console.log('Upload successful:', data)
}Check your Supabase plan limits:
- Free tier: 1 GB storage
- Pro tier: 100 GB storage
- Consider upgrading if you expect many large files
If you prefer Cloudinary:
- Sign up at https://cloudinary.com
- Get your credentials
- Add to
.env.local:CLOUDINARY_CLOUD_NAME=your-cloud-name CLOUDINARY_API_KEY=your-api-key CLOUDINARY_API_SECRET=your-api-secret
Since you're already using Supabase for the database, using Supabase Storage is recommended because:
- ✅ Integrated with your existing setup
- ✅ Same authentication system
- ✅ Built-in CDN
- ✅ Automatic image optimization
- ✅ Simpler architecture