guide

How to Use Supabase with Lovable or Bolt (Step-by-Step)

Connect Supabase with Lovable or Bolt.new to add auth, row-level security, and file storage to your AI-built app — no backend experience needed.

Caleb NorthBy Caleb North · The ship-it engineerMay 10, 2026
Verified May 2026

Caleb North is a fictional AI persona, not a real person. This article was written by AI and reviewed by a human editor before publishing. How we work →

How to Use Supabase with Lovable or Bolt (Step-by-Step)

Some links may be affiliate links. We may earn a commission at no extra cost to you.

Every and Bolt tutorial ends the same way: "Look, it works in the browser!" Then real users show up, and the whole thing collapses — no login, no data isolation, anyone can read anyone else's rows. This guide picks up exactly where the builder stops.

You'll connect a real project, turn on auth that actually locks down data, and add file storage — explained for someone who has never touched a database before. If you're still deciding which builder to use, read the Lovable vs Bolt vs Replit comparison first, then come back here.

What Supabase Actually Does for Your App

The three things most AI-built apps need but don't have

An AI builder like Lovable or Bolt can scaffold a beautiful UI in minutes. What it can't give you by default is a real backend. Most apps that survive actual users need three things:

  • A database — persistent storage that lives beyond a browser session
  • Authentication — a system that knows who a user is and keeps their session alive
  • Row-level security — rules that make sure user A can never read user B's data

Without all three, your app is a demo, not a product.

How Supabase fits alongside Lovable and Bolt.new

Supabase is a hosted Postgres database wrapped in useful things: an auth system, a file storage service, and a set of auto-generated APIs. You don't install anything locally. You create a project on supabase.com and connect it to your builder.

Lovable has a native Supabase integration that wires most of this up automatically. Bolt.new requires a few more manual steps, but the end result is the same. Both use the official supabase-js client library to talk to your Supabase project.

Before You Start: What You Need

Creating a free Supabase project (what each setting means)

Go to supabase.com and create an account. Hit "New project" and you'll see three fields:

  • Project name — anything you want, just make it recognizable
  • Database password — Supabase auto-generates one. Save it somewhere. You'll rarely need it directly, but you'll want it if you ever connect a database GUI like TablePlus.
  • Region — pick the one geographically closest to your users. This reduces latency.

The free tier includes two projects, 500 MB of database storage, 50,000 monthly active users for auth, and 1 GB of file storage. These limits are generous enough for a real side project or small launch.

Where to find your project URL and anon key

Once the project spins up (takes about 30 seconds), go to Project Settings → API. You'll see two values you'll need constantly:

  • Project URL — looks like https://xyzxyz.supabase.co
  • Anon key — a long JWT string labeled anon public

Copy both. These are what connect your frontend code to your Supabase project. Never confuse the anon key with the service role key — more on that in the Bolt section.

Connecting Supabase to Lovable

Using Lovable's native Supabase integration (the fast path)

Open your Lovable project. In the left sidebar, look for the Supabase tab (it's the database icon). Click "Connect to Supabase," paste in your project URL and anon key, and hit connect.

That's it for the connection step. Lovable stores your credentials securely in its project settings and injects them as environment variables at build time. You don't need to touch any config files manually.

What Lovable actually does under the hood when it connects

When you connect Supabase, Lovable installs @supabase/supabase-js as a dependency and typically creates a src/integrations/supabase/client.ts file that initializes the Supabase client with your URL and anon key. Every component that needs database access imports from that file.

This means if you ever need to write a custom prompt — "add a query to fetch all posts for the current user" — the AI already has the client available. You don't need to set it up yourself.

Confirming the connection worked

The simplest test: ask Lovable to create a table in your Supabase database. Try something like: "Create a notes table with columns for id, user_id, content, and created_at." If the integration is working, Lovable will run a migration and the table will appear in your Supabase dashboard under Table Editor.

If the table doesn't appear, double-check that you pasted the correct project URL (not the API URL — they look similar but are different fields in the Supabase dashboard).

Connecting Supabase to Bolt.new

Adding your Supabase credentials via environment variables

In your project, open the Environment Variables panel (usually accessible from the project settings). Add two variables:

VITE_SUPABASE_URL=https://xyzxyz.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key-here

The VITE_ prefix is required if your Bolt project uses Vite (most do). Without it, the variable won't be accessible in your frontend code.

Prompting Bolt to wire in the Supabase JS client

Once the environment variables are set, give Bolt this prompt:

Install @supabase/supabase-js and create a supabaseClient.ts file in src/lib/ 
that initializes the Supabase client using import.meta.env.VITE_SUPABASE_URL 
and import.meta.env.VITE_SUPABASE_ANON_KEY. Export the client as the default export.

Bolt will install the package and create the file. From that point on, any feature prompt you write — auth, database queries, storage — should include "use the Supabase client from src/lib/supabaseClient.ts" so Bolt knows where to find it.

Common error: anon key vs service role key (never use service role client-side)

Your Supabase project has two keys. The anon key is safe to use in frontend code — it's public by design, and your row-level security policies (covered below) are what actually enforce access control. The service role key bypasses all security policies entirely. It is for server-side admin tasks only.

If you ever paste the service role key into Bolt or Lovable, any user who inspects your app's network traffic can extract it and read or delete every row in your database. Keep it out of any client-side code. For more on keeping secrets safe, see how to keep secrets safe in AI-generated code.

Adding Auth That Actually Works

Email/password sign-up and login in under 10 minutes

In Lovable, prompt: "Add a sign-up and login page using Supabase email/password auth. After login, redirect to /dashboard." Lovable's AI knows the Supabase auth API and will generate the sign-up and sign-in calls correctly.

In Bolt, prompt: "Add a sign-up form that calls supabase.auth.signUp with email and password, and a login form that calls supabase.auth.signInWithPassword. Show the dashboard route only when the user is logged in. Use the Supabase client from src/lib/supabaseClient.ts."

Both calls use the supabase-js v2 API. If you see a prompt telling you to use supabase.auth.signIn (without "WithPassword"), that's the old v1 API and it won't work.

What "auth.uid()" is and why it matters for everything else

When a user logs in, Supabase generates a session with a unique user ID — a UUID like a4f3.... In your database, this is accessible as auth.uid(). It's the value that ties a row of data to a specific user.

Every table that belongs to users should have a user_id column that stores this value. When you insert a row on behalf of a logged-in user, you set user_id to auth.uid(). When you write security policies (next section), you check that user_id = auth.uid() before allowing access. This one value is the foundation of every security policy you'll write.

Testing that login genuinely works (log in as two different users)

Don't just test that you can log in. Open a second browser (or an incognito window) and create a second account. Add a note as user 1. Log in as user 2. Confirm that user 2 cannot see user 1's note.

If user 2 can see user 1's data, your row-level security isn't set up yet — which is normal at this point. The next section covers exactly that.

Row-Level Security: Locking Down Your Data

Why RLS is off by default and why that's dangerous for a live app

Supabase creates tables with row-level security disabled. That means any request made with your anon key can read every row in the table. Since your anon key is in your frontend code (and anyone can find it with browser dev tools), this is essentially a public API with no access control.

Lovable's native integration does not automatically enable RLS on tables it generates. You have to turn it on yourself. This is one of the most important things to do before launching any app with real user data.

The one RLS policy every beginner app needs (users see only their own rows)

Go to your Supabase dashboard, open Table Editor, click your table (e.g., notes), and then open Auth → Policies. Enable RLS on the table, then create a new policy.

You need four policies for full CRUD — but start with the most critical one: SELECT (reading). This controls what data a logged-in user can retrieve.

How to write it: the exact SQL with plain-English explanation of each part

CREATE POLICY "Users can view their own notes"
ON notes
FOR SELECT
USING (auth.uid() = user_id);

Plain English breakdown:

  • CREATE POLICY "Users can view their own notes" — the name, just for your reference
  • ON notes — which table this applies to
  • FOR SELECT — this policy controls read operations
  • USING (auth.uid() = user_id) — only return rows where the user_id column matches the ID of the currently logged-in user

For a complete app, you also need INSERT, UPDATE, and DELETE policies. They follow the same pattern — replace FOR SELECT with FOR INSERT, FOR UPDATE, or FOR DELETE, and replace USING with WITH CHECK for INSERT and UPDATE.

-- Let users insert only rows with their own user_id
CREATE POLICY "Users can insert their own notes"
ON notes
FOR INSERT
WITH CHECK (auth.uid() = user_id);

-- Let users update only their own rows
CREATE POLICY "Users can update their own notes"
ON notes
FOR UPDATE
USING (auth.uid() = user_id);

-- Let users delete only their own rows
CREATE POLICY "Users can delete their own notes"
ON notes
FOR DELETE
USING (auth.uid() = user_id);

Testing your policy: the only way to know it works

Back in your app, log in as user 1, insert a note, then log out. Log in as user 2. Try to fetch notes. If your policy is working, user 2's query returns an empty array — not an error, just no rows. An empty result means the policy is filtering correctly.

If you want to test this in SQL directly, the Supabase dashboard has a SQL Editor. You can run queries as a specific user by passing their JWT — but the two-browser test above is faster and proves the end-to-end behavior actually works in your real app.

The most common RLS mistake (and how to spot it in your own project)

The most common mistake is enabling RLS but forgetting to add any policies. When RLS is on with no policies, the default behavior is to deny everything — including reads by the row's owner. Your app will appear broken because no data loads, and the error messages from Supabase won't make it obvious why.

If your queries suddenly return empty results after enabling RLS, check that you have at least one SELECT policy on the table. Open Auth → Policies in the dashboard and confirm the policy list isn't empty.

File Storage: Letting Users Upload Files

Creating a storage bucket in Supabase

Go to Storage in your Supabase dashboard and click "New bucket." Name it something descriptive — avatars, uploads, or documents. Set it to private (not public) unless you specifically want files to be viewable by anyone without authentication.

A private bucket requires a signed URL to access files — Supabase generates these on request. This is the correct default for user-uploaded content.

Prompting Lovable or Bolt to add a file upload input

In Lovable, try: "Add a file upload button. When a user selects a file, upload it to the Supabase uploads bucket under a path like {user_id}/{filename}. Show a success message when the upload completes."

In Bolt, be more explicit:

Add a file input element. On change, call supabase.storage
  .from('uploads')
  .upload(`${user.id}/${file.name}`, file)
using the Supabase client from src/lib/supabaseClient.ts.
Log the result and show an error message if it fails.

The folder structure {user_id}/{filename} is intentional — it makes your storage policies much simpler to write.

Setting storage policies so users can only access their own uploads

Storage policies work the same way as table policies. In the Supabase dashboard, open Storage → Policies and add a policy on your bucket. To let users read only their own files:

CREATE POLICY "Users can access their own uploads"
ON storage.objects
FOR SELECT
USING (auth.uid()::text = (storage.foldername(name))[1]);

storage.foldername(name)[1] extracts the first folder segment from the file path — which, if you followed the {user_id}/{filename} pattern above, is the user's UUID. This policy denies access to any file not in that user's folder.

What to Do When Things Break

Reading Supabase error messages for the first time

Supabase errors come back as JSON objects with a message and a code. The most common ones you'll hit:

  • new row violates row-level security policy — you're trying to INSERT a row but your INSERT policy is missing or the user_id column isn't being set correctly
  • JWT expired — the user's session has timed out. Your app needs to call supabase.auth.refreshSession() or redirect to login
  • relation "table_name" does not exist — the table hasn't been created yet, or you're querying the wrong schema
  • permission denied for table — RLS is on and no matching policy exists

When you get an error in the browser console, copy the full JSON object and paste it into your builder prompt. Both Lovable and Bolt are good at interpreting Supabase errors when you give them the exact message.

Using the Supabase MCP with Claude Code to debug without opening the dashboard

If you're using Claude Code alongside your builder, the Supabase MCP server lets Claude query your database, inspect your table schema, and read your RLS policies directly from the terminal — no need to click through the Supabase dashboard.

This is especially useful for debugging policy logic. You can ask Claude: "Check the RLS policies on my notes table and tell me why a logged-in user might be getting an empty result." Claude will pull the policy definitions and explain what's happening.

For pure database needs without auth or storage, Neon is worth considering as an alternative — it's a serverless Postgres database that's lighter to configure. If you want auth without a full database backend, Clerk is a simpler option that Lovable and Bolt both support.


The path here is the same regardless of which builder you're using: connect Supabase, enable auth, turn on RLS, write one policy per operation per table. That's the entire backend for most beginner apps. Start with the notes table pattern above and it will work for any data type that belongs to individual users.

Get started with Lovable at lovable.dev or sign up for a free Supabase project at supabase.com.

The StackBrief weekly

New reviews and the AI-coding-tool news worth knowing — with our take. One email a week, unsubscribe anytime.

Keep reading