Supabase is your project's database, login system, and file storage — all in one place. Think of it as three things wrapped together: a spreadsheet (for storing data), a security guard (for controlling who sees what), and a lock system (for user accounts and passwords).
You don't have to build any of this yourself. Supabase does it.
A table is like a spreadsheet tab. One table for users. One for orders. One for messages. Each row is one record — one user, one order, one message. Each column is one piece of information about that record.
You create and edit tables in the Supabase dashboard under Table Editor — no code needed for the basics.
The most common mistake: Trying to add data before the table exists, or putting data in the wrong table.
What it is: A lock on every single row in every table. When RLS is on and no rules are written, nobody can read or write anything — not even your own app.
Why it exists: Without RLS, anyone who got hold of your Supabase key could read your entire database. RLS makes sure people only see the data they're supposed to.
The two-step trap that gets everyone:
- Step 1: Turn RLS on (most people do this)
- Step 2: Write "policies" — rules about who can see and do what (most people skip this and wonder why nothing works)
Setting up basic policies: In the Supabase dashboard: Authentication → Policies → New Policy
Or paste this in the SQL Editor and adjust the table name:
-- Anyone can read (public data)
CREATE POLICY "allow_read" ON your_table
FOR SELECT USING (true);
-- Only logged-in users can add new rows
CREATE POLICY "allow_insert" ON your_table
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);
-- Users can only edit their own rows
CREATE POLICY "allow_update_own" ON your_table
FOR UPDATE USING (auth.uid() = user_id);For testing: You can temporarily disable RLS on a table (Table Editor → table → RLS toggle). Never leave it off in production — that means anyone can read everything.
Both are on your Supabase dashboard under Settings → API.
anon key — safe to put in your frontend code. Limited access. Respects your RLS policies. Use this one in React, Next.js, etc.
service_role key — has admin access to everything, bypasses RLS. NEVER put this in frontend code or push it to GitHub. This is for server-side only (backend, edge functions, scripts run on your VPS).
If your service_role key ever shows up in a GitHub commit, rotate it immediately in the Supabase dashboard.
Supabase Auth handles signups, logins, and password resets — you don't build any of this yourself.
// Let someone sign up
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'their-password'
})
// Let someone log in
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'their-password'
})
// Find out who's logged in right now
const { data: { user } } = await supabase.auth.getUser()
// Log out
await supabase.auth.signOut()After a user logs in, Supabase automatically attaches their identity to every database request. This is how RLS policies can say "only show this user their own rows" — Supabase knows who's asking.
Edge Functions are small pieces of server-side code that run on Supabase's servers (not your computer, not Vercel). Use them when you need to do something secretly — like calling an API without your secret key being visible in your frontend code.
Think of them as: a private back room where your app can do things the public website shouldn't.
# Deploy a function
supabase functions deploy function-name
# Call it from your app
const { data, error } = await supabase.functions.invoke('function-name', {
body: { the: 'data you want to send' }
})By default, your tables live in the "public" schema. If you or Claude created a custom schema (like "aridunu", "app", "internal"), two separate steps are needed to make it work:
Step 1: Tell Supabase's API about it Go to: Settings → API → "Extra schemas to expose" → add your schema name
Step 2: Grant access in SQL Editor:
GRANT USAGE ON SCHEMA yourschema TO anon, authenticated;
GRANT ALL ON ALL TABLES IN SCHEMA yourschema TO anon, authenticated;Skipping either step causes "permission denied for schema" errors. Both steps are required.
You need two things from your Supabase dashboard (Settings → API):
- Your Project URL — looks like
https://abcdefgh.supabase.co - Your anon key — a long string starting with
eyJ...
These go in your .env file:
NEXT_PUBLIC_SUPABASE_URL=https://abcdefgh.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
Then in your code:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)On Vercel: These environment variables need to be added in Vercel's dashboard too (Settings → Environment Variables). Your local .env file doesn't automatically go to Vercel.