Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.refactkit.com/llms.txt

Use this file to discover all available pages before exploring further.

RefactKit uses Supabase Storage — an S3-compatible object store — to handle user avatars, organization logos, and gallery images. All file uploads run server-side through a dedicated server function, which keeps your service role key out of the browser. This page covers retrieving your credentials, creating the required bucket, and setting the correct access policy.

Prerequisites

Environment variables

Storage requires two variables in your .env.local:
VITE_SUPABASE_URL="https://xxxxxxxxxxxx.supabase.co"
SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
SUPABASE_SERVICE_ROLE_KEY bypasses Supabase Row Level Security entirely. It must never be prefixed with VITE_ and must never be imported in any client-side module. Keep it server-only. RefactKit enforces this by only referencing it in src/server/storage-fns.ts, which runs exclusively on the Nitro server.

Steps

1

Get your project URL and service role key

  1. In your Supabase project dashboard, go to Project Settings → API.
  2. Under Project URL, copy the URL — this is your VITE_SUPABASE_URL.
  3. Under Project API keys, find the service_role entry (labeled “secret”) and copy that value — this is your SUPABASE_SERVICE_ROLE_KEY.
VITE_SUPABASE_URL="https://xxxxxxxxxxxx.supabase.co"
SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
2

Create the avatars bucket

RefactKit expects a bucket named avatars with public read access. You can create it via SQL or through the Supabase dashboard.
In your Supabase project, go to SQL Editor and run the following:
-- Create the avatars bucket (public read)
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true)
ON CONFLICT (id) DO NOTHING;
The ON CONFLICT clause makes the statement safe to re-run — it will not fail if the bucket already exists.
3

Set the public read policy

The bucket being public controls the URL format, but you also need a storage policy that allows unauthenticated SELECT access. Run this in the SQL Editor:
-- Allow public read access for avatar images
CREATE POLICY "Public Access" ON storage.objects
FOR SELECT USING (bucket_id = 'avatars');
This policy lets anyone with the file URL retrieve avatar images — which is required for displaying user and organization avatars in the UI without requiring a signed URL on every request.
Upload operations are always authenticated and run through the server function in src/server/storage-fns.ts using the service role key. The public policy only applies to read (GET) access.

How uploads work

All file uploads go through a server function to protect the service role key:
  1. The client sends the file as FormData to the server function.
  2. The server validates file size (max 2 MB) and content type.
  3. A random filename is generated to prevent collisions.
  4. The file is uploaded to Supabase Storage via the service role client.
  5. The public URL is returned to the client and stored in the database.
The upload logic lives in src/server/storage-fns.ts. The Supabase client for storage is initialized in lib/supabase.ts using SUPABASE_SERVICE_ROLE_KEY.

Variable reference

VariableWhere to find itClient-safe?
VITE_SUPABASE_URLSupabase → Project Settings → API → Project URLYes — safe to expose in the browser
SUPABASE_SERVICE_ROLE_KEYSupabase → Project Settings → API → service_role keyNo — server-only, never prefix with VITE_
If you add more buckets (for example, a gallery bucket), repeat the same SQL pattern: insert the bucket with public = true and add a SELECT policy scoped to that bucket_id.