Take your app from static to dynamic. Read real data, save user input, sync across devices.
Don't worry — you won't write any database code by hand. Your AI tool handles all the technical parts. This guide helps you understand the concepts so you can describe what you want.
Your app and your database live in different places. Your app runs in a browser or on Vercel. Your database runs on Supabase's servers. To talk to each other, they need three things:
URL
The address of the database. Like a phone number.
API key
The password that lets your app in. Kept secret in .env.local.
Client library
The little helper installed in your project that handles all the talking.
Here's the fastest way to go from no database to a working connection:
Create a free Supabase account at supabase.com
Click "New project", give it a name, set a password (save it somewhere!)
Wait ~2 minutes while Supabase provisions your database
Click "Project Settings → API" and copy two values: the Project URL and the anon key
Paste both into your AI tool with the prompt below
HOOK SUPABASE INTO MY PROJECT
"Connect Supabase to my project. My Project URL is [paste URL] and my public key is [paste key]. Set up everything needed to start using Supabase, and keep my keys in a safe place so they're never pushed to GitHub. Confirm it's working by loading a row or two from any of my tables."
Once Supabase is connected, you can pull data into any page. There are two ways to do it: on the server (faster, better SEO) and in the browser (more interactive).
Server-side (preferred)
The data is fetched before the page is sent to the user. They see the data instantly. Google can read it. Best for blog posts, product pages, anything that doesn't change every second.
Use when: data is the same for everyone or doesn't change often.
Client-side
The data is fetched in the browser after the page loads. The user sees a loading state first, then the data appears. Best for live updates, user-specific data, dashboards.
Use when: data is personal, changes often, or needs to update without a page reload.
ADD DATA TO A PAGE
"On my /products page, fetch all rows from the products table in Supabase and display them as cards. Use server-side rendering so the data is available on first load. Each card should show the product name, price, and image. Sort by newest first."
Reading is easy. Writing — saving what users type — is where it gets interesting. Here's the typical flow:
User types into a form (name, email, message)
User clicks Submit
Your app sends the data to Supabase using INSERT
Supabase saves the row and sends back a confirmation
Your app shows a success toast and clears the form
SAVE A FORM TO THE DATABASE
"On my /contact page, when the user submits the form, save the data (name, email, message) to a contact_messages table in Supabase. Show a loading state while saving, a success toast when it works, and an error toast if it fails. Clear the form after a successful save."
By default, Supabase blocks every query you send from the browser. That's a feature, not a bug — it means random people can't read your database just because they know your URL.
To allow queries, you write RLS policies. A policy is a rule like "authenticated users can read their own rows" or "anyone can read published posts." Without policies, your app sees zero data.
Common RLS policies (in plain English)
ADD RLS POLICIES
"My posts table has security turned on but no rules set, so my app can't see anything. Add these rules: anyone can read published posts, signed-in users can create new posts, and each user can only edit or delete their own posts. Walk me through what each rule does in plain English."
"Empty array" returned from a query when data clearly exists
RLS is blocking it. Check the table's policies in Supabase or ask AI to add a SELECT policy.
"Failed to fetch" in the browser console
Your environment variables aren't loading. Restart the dev server after editing .env.local.
"Permission denied for table users"
You're trying to query auth.users directly. Use a profiles table that links to auth.users via user_id instead.
Data shows up locally but not in production
You forgot to add the env vars to Vercel. Settings → Environment Variables → paste them in.
You built a todo app where tasks live in browser memory. Refresh the page and they vanish. Now you want them to persist forever, sync across devices, and stay private to each user. Connect Supabase, create a tasks table with RLS, and rewire the app to read/write from the database instead of memory.
Build this with AI
"My todo app keeps tasks only in the browser's memory, so they disappear when I refresh the page. Set up Supabase, create a tasks table (id, which user it belongs to, title, whether it's done, and when it was created), and make sure each signed-in visitor only ever sees their own tasks. Rewrite my app so tasks are loaded from Supabase when the page opens, new tasks get saved there, and checking or deleting a task updates Supabase too. Show a loading spinner while tasks are loading and a friendly message if something goes wrong."
REAL-TIME SUBSCRIPTION
"In my chat app, subscribe to new messages in the messages table using Supabase Realtime. When a new message is inserted, automatically add it to the displayed message list without a page reload. Clean up the subscription when the component unmounts."
OPTIMISTIC UPDATES
"When the user toggles a checkbox in my todo app, update the UI instantly (don't wait for the database). If the database write fails, revert the UI change and show an error toast. This makes the app feel much faster."
PAGINATION
"My posts page shows all posts at once and it's slow with 500+ rows. Add pagination: 20 posts per page, with Previous/Next buttons. Use Supabase's range() helper to fetch only the right slice each time."