These 22 prompts build a real Supabase backend with Bolt.new — the in-browser, full-stack AI app builder from StackBlitz. Bolt connects Supabase through its built-in integration (the Connect Supabase button, not a chat command), then wires the client and env vars for you. Because Bolt runs in WebContainer — a browser Node.js runtime with no local database — Supabase supplies hosted Postgres, auth, storage, realtime, and edge functions your app talks to over the network.
Every prompt below is complete text you paste straight into the Bolt chat box, with [bracketed placeholders] for your own names. Each one pins the frontend stack, names the exact tables, columns, and relationships, spells out RLS policies in plain language, and scopes edits to named files to save tokens. For the wider set, keep the 40 best Bolt.new prompts roundup handy; for the app layer on top of this backend, see the full-stack app prompts.
Connect Supabase & the schema
Start by connecting Supabase through the toolbar, then let Bolt design the data model. Describe tables, columns, types, and relationships in plain language and Bolt generates and applies the SQL migration against your hosted Postgres project.
1. Connect Supabase and pin the stack
I've connected a Supabase project with the Connect Supabase button. Set up the app foundation to use it.
App: [a personal notes app] built on Vite + React + TypeScript + Tailwind, using Supabase for the database and auth.
Do this:
- Create src/lib/supabase.ts that initializes the Supabase client from the VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY environment variables the integration already set. Never hardcode the URL or keys.
- Add a typed helper so components import one client, not many.
- Add a small "connection OK" check on the home page that reads the current session and confirms the client is wired.
Remember this runs in WebContainer with no local database — all data lives in hosted Supabase Postgres. Don't build any tables yet; I'll describe the schema next.Why it works: it treats the integration as already connected and points Bolt at the env vars it set, so the client is wired correctly before any schema exists.
2. Plan the schema before building
Before writing any SQL, plan the Supabase data model for this app so we can agree on it first.
App: [a habit tracker] used by [individuals] to [log daily habits and see streaks].
In the plan, list for each table:
- Columns with Postgres types, primary keys, and defaults (e.g. id uuid default gen_random_uuid(), created_at timestamptz default now()).
- Foreign keys and the relationships between tables.
- Which columns need indexes because we'll filter or sort by them.
- The row-level security policy in plain language (who can select, insert, update, delete each row).
Tables I expect: [Profile, Habit, Checkin]. Output only the plan as markdown — a table-by-table spec. Do NOT create anything yet; I'll review it, then ask you to generate the migration.Best for: a bigger backend where a plan-first pass pins the tables and RLS before a single token is spent on the migration.
3. Generate a relational schema with indexes
Create the Supabase schema for this app and apply it as a migration to the connected project.
Tables:
- profiles: id uuid primary key references auth.users, email text, full_name text, created_at timestamptz default now().
- projects: id uuid primary key default gen_random_uuid(), owner_id uuid references profiles(id), name text not null, status text check (status in ('planning','active','done')) default 'planning', created_at timestamptz default now(), updated_at timestamptz default now().
- tasks: id uuid primary key default gen_random_uuid(), project_id uuid references projects(id) on delete cascade, title text not null, done boolean default false, due_date date, created_at timestamptz default now().
Also:
- Add indexes on projects(owner_id), tasks(project_id), and tasks(done).
- Enable row-level security on every table (I'll define the policies in the next message).
- Add an updated_at trigger on projects.
Generate the SQL migration, apply it through the Supabase integration, and confirm the tables exist. Don't build UI yet.Why it works: giving each column a type, foreign key, and index lets Bolt emit one clean migration instead of guessing the shape and correcting it later.
4. Add a table to an existing schema
Add one table to the existing Supabase schema — don't touch the tables that already exist.
New table: comments — id uuid primary key default gen_random_uuid(), task_id uuid references tasks(id) on delete cascade, author_id uuid references profiles(id), body text not null, created_at timestamptz default now().
Also:
- Index comments(task_id).
- Enable RLS: a user can select comments on tasks in projects they own, insert a comment as themselves (author_id = auth.uid()), and update or delete only their own comments.
Generate a new migration for just this change and apply it. Leave the projects and tasks tables and their policies exactly as they are.Best for: evolving a live schema safely, where scoping the change to one new table and its own migration avoids collateral edits.
5. Seed realistic sample data
Seed the connected Supabase database with realistic sample data so the app doesn't look empty in development.
Do this:
- Insert [3 sample projects] for the current signed-in user, each with [4 to 6 tasks] at a mix of done and not-done states and a spread of due dates across the next two weeks.
- Make the data look real — plausible project names and task titles, not "test 1 / test 2".
- Put the seed in a repeatable SQL file (supabase/seed.sql) that's safe to re-run: delete this user's sample rows first, then insert.
Apply it once. Tell me how to re-run the seed later. Don't change the schema.Why it works: a repeatable, user-scoped seed keeps screens populated during iteration without polluting the schema or other users' rows.
Auth & user accounts
Add Supabase Auth as its own step and name the sign-in methods, the redirect behavior, and whether a profile row is created on first login. Pair every auth flow with the matching RLS so the login boundary and the data boundary are the same.
6. Email and password auth with protected routes
Add authentication using Supabase Auth (email + password).
Product surface:
- /login and /signup pages with clear inline validation and error messages.
- A session that persists across refreshes, and a sign-out button.
- Every route under /app requires a session; unauthenticated visitors are redirected to /login.
Do this:
- Use the existing Supabase client in src/lib/supabase.ts. Read keys from env only.
- Add an AuthProvider (React context) exposing the current user and session; a useAuth() hook for components.
- Add a route guard component that redirects when there's no session, checked on the client and re-validated against Supabase on load.
- On first sign-up, insert a matching row into the profiles table (id = auth user id, email).
Stack: Vite + React + TypeScript + Tailwind. Only create the auth files and the guard; don't restyle existing pages.Why it works: naming the provider, the guard, and the profile insert makes Bolt build one coherent auth flow instead of a login form that forgets to create the user's row.
7. Magic-link auth with profile creation
Set up passwordless magic-link auth with Supabase Auth.
Product surface:
- /login with a single email field and a "Send magic link" button.
- A "check your email" confirmation state, and an /auth/callback route that completes the session and redirects to /app.
- First-time sign-in creates the user's profiles row automatically.
Do this:
- Use signInWithOtp on the Supabase client; set the email redirect to /auth/callback.
- On the callback, exchange the code for a session, then upsert a profiles row (id = auth.uid(), email, created_at).
- Handle expired or already-used links with a clear error and a "resend link" option.
Tell me exactly which Supabase Auth dashboard settings (Site URL, redirect URLs, email provider) I must set for magic links to work. Stack: Vite + React + TypeScript + Tailwind. Only edit the auth files.Best for: low-friction consumer sign-in where the callback exchange and profile upsert are handled end to end.
8. GitHub and Google OAuth sign-in
Add OAuth sign-in with GitHub and Google using Supabase Auth, alongside the existing email login.
Product surface:
- On /login, add "Continue with GitHub" and "Continue with Google" buttons above the email form.
- After OAuth, land the user on /app with a persisted session.
- On first OAuth sign-in, create the profiles row from the provider's name and email if it doesn't exist.
Do this:
- Use signInWithOAuth with an /auth/callback redirect that completes the session.
- Keep provider config out of code — tell me the exact Supabase dashboard steps and the callback URL to register in the GitHub and Google OAuth apps.
- Don't store any client secret in the frontend; OAuth secrets live in the Supabase dashboard.
Stack: Vite + React + TypeScript + Tailwind. Scope edits to the login page and the callback route.Why it works: it keeps OAuth secrets in the Supabase dashboard and asks for the exact provider setup, so the flow is secure and actually configurable.
Row-level security & queries
Supabase exposes tables through a public API, so a table without row-level security is open to anyone with the anon key. Describe each policy in plain language — usually scoped to auth.uid() — and let Bolt generate the SQL beside the schema.
9. Row-level security for user-owned data
Add row-level security to the projects and tasks tables so each user only touches their own data.
Policies in plain language:
- projects: a user can SELECT, UPDATE, and DELETE only rows where owner_id = auth.uid(); on INSERT, owner_id must equal auth.uid().
- tasks: a user can SELECT, INSERT, UPDATE, and DELETE only tasks whose project belongs to them (project's owner_id = auth.uid()); enforce with a subquery or a join in the policy.
- profiles: a user can SELECT and UPDATE only their own row (id = auth.uid()).
Do this:
- Make sure RLS is enabled on all three tables.
- Generate the SQL policies and apply them as a migration.
- After applying, confirm that with the anon key, a signed-in user can't read another user's projects.
Don't change the table columns. Only add policies.Why it works: policies stated per operation and scoped to auth.uid() close the public-API gap that leaves Supabase tables readable by anyone with the anon key.
10. Role-based RLS with an is_admin check
Add an admin role and role-aware RLS on top of the existing policies.
Do this:
- Add a role column to profiles: role text check (role in ('user','admin')) default 'user'.
- Create a Postgres helper function is_admin() that returns true when the current auth.uid() has role = 'admin' (security definer, stable).
- Update the projects and tasks policies so that admins can SELECT and DELETE any row, while normal users stay scoped to their own (owner_id = auth.uid()).
- Keep INSERT restricted so a user can only create rows as themselves, regardless of role.
Generate the migration and apply it. Confirm a non-admin still can't see others' rows and an admin can. Don't touch the UI in this step.Best for: apps that need an admin view without abandoning per-user isolation, using one is_admin() helper reused across policies.
11. CRUD screens on a Supabase table
Build the CRUD UI for the projects table against Supabase.
Product surface:
- /app/projects: a list of the current user's projects (name, status, updated) with an empty state.
- A "New project" dialog with a validated form.
- Row actions: edit inline or in a dialog, and delete with a confirm step.
Do this:
- All reads and writes go through the Supabase client in src/lib/supabase.ts; rely on RLS to scope rows to the user (don't re-filter by owner in the client).
- Validate input before insert/update; show Supabase errors as friendly toasts.
- After a write, refetch or optimistically update the list so it stays fresh.
Stack: Vite + React + TypeScript + Tailwind. Put data calls in src/lib/projects.ts and keep components thin. Only create these files.Why it works: leaning on RLS for scoping and isolating queries in one module keeps the create-edit-delete loop correct and the components thin.
12. Server-side search, filter, and pagination
Make the tasks list scale: search, filter, and pagination pushed into the Supabase query, not done in the browser.
Product surface:
- A search box (matches task title), a status filter (all / open / done), and a due-date sort.
- Pagination or infinite scroll — load a page at a time, not the whole table.
Do this:
- Build the query with the supabase-js filter methods: ilike for search, eq for status, order for sort, and range() for pagination.
- Add a database index on tasks(title) if search feels slow, and rely on the existing tasks(project_id) and tasks(done) indexes.
- Keep the current filter and page in the URL query string so results are shareable and refresh-safe.
Only edit the tasks list files and src/lib/tasks.ts. Stack: Vite + React + TypeScript + Tailwind.Best for: a list that stays fast as rows grow, because filtering and paging happen in Postgres via range() and ilike, not in memory.
13. Multi-tenant workspaces with membership RLS
Turn this single-user app into multi-tenant workspaces on Supabase.
Schema changes:
- workspaces: id uuid pk default gen_random_uuid(), name text, created_at timestamptz default now().
- memberships: id uuid pk, workspace_id uuid references workspaces(id) on delete cascade, user_id uuid references profiles(id), role text check (role in ('owner','admin','member')) default 'member'. Unique (workspace_id, user_id).
- Add workspace_id to projects (and cascade tasks through projects).
RLS in plain language:
- A user can only see a workspace, and rows inside it, if they have a membership row for that workspace (workspace_id in the set of their memberships).
- Only owners and admins can insert or delete projects; members can read and update.
Add a helper function that returns the current user's workspace ids to keep the policies readable. Generate the migration, apply it, and add a workspace switcher in the sidebar. Scope UI edits to the sidebar and a workspace context file.Why it works: membership-based policies plus a helper listing the user's workspace ids give real tenant isolation instead of a UI that only looks separated.
14. A Postgres view and an RPC function
Move some aggregation into Postgres instead of computing it in the client.
Do this:
- Create a view project_stats that, per project, returns project_id, total_tasks, done_tasks, and completion_pct. Respect RLS so a user only sees stats for their own projects.
- Create an RPC function get_dashboard_summary() that returns the current user's totals: open tasks, tasks due this week, and completed this week — computed in SQL from auth.uid().
- Expose the function so the client can call it with supabase.rpc('get_dashboard_summary').
Generate the migration, apply it, then have the /app dashboard read from the view and the RPC instead of pulling all rows. Only edit the dashboard data file and add the migration.Best for: dashboards that should aggregate in the database through a view and an RPC rather than fetch everything and reduce in the browser.
Storage & realtime
Supabase Storage and realtime both work from inside WebContainer because they run against hosted Supabase. Name the bucket's privacy, the upload rules and storage policy, and — for realtime — the exact table changes the UI should subscribe to.
15. Private storage bucket with owner policy
Add file uploads with a private Supabase Storage bucket.
Requirements:
- Create a private bucket named [documents].
- Users upload files to a path prefixed with their user id (e.g. {auth.uid()}/filename).
- A storage RLS policy so a user can only read, upload, and delete objects under their own {auth.uid()}/ prefix.
- Track uploads in a documents table: id, user_id, path, filename, size, mime_type, created_at — with RLS scoping rows to the owner.
Build the upload UI:
- Validate file type ([pdf, png, jpg]) and max size ([10 MB]) on the client before upload.
- After upload, insert the documents row. Serve downloads with a short-lived signed URL, since the bucket is private.
Stack: Vite + React + TypeScript + Tailwind. Put storage helpers in src/lib/storage.ts. Only add these files and the migration.Why it works: the owner-prefixed path plus a storage policy and signed URLs keep private files private even though the bucket is reachable through the public API.
16. Public image gallery with uploads
Build an image gallery backed by a public Supabase Storage bucket.
Requirements:
- Create a public bucket named [gallery] for images anyone can view.
- Only signed-in users can upload; a storage policy allows insert for authenticated users and public read.
- An images table: id, uploader_id, path, caption, created_at, with RLS letting anyone SELECT but only the uploader UPDATE or DELETE their own row.
Build the UI:
- A responsive grid reading public URLs from storage.
- An "Upload image" form (auth required) with client-side type and size validation and an optimistic tile while it uploads.
Stack: Vite + React + TypeScript + Tailwind. Keep storage and DB calls in src/lib/gallery.ts. Only create these files and the migration.Best for: public content where reads are open but writes stay tied to the uploader through both storage and table policies.
17. Realtime chat on Postgres changes
Build a realtime chat feature using Supabase realtime (Postgres changes).
Schema:
- rooms: id uuid pk, name text, created_at.
- messages: id uuid pk, room_id uuid references rooms(id) on delete cascade, sender_id uuid references profiles(id), body text not null, created_at timestamptz default now(). Index messages(room_id, created_at).
RLS:
- A user can read messages in rooms they're a member of and insert messages only as themselves (sender_id = auth.uid()).
Realtime:
- Subscribe to INSERT events on messages filtered by the open room_id, and append new messages to the list live — no polling.
- Enable the messages table for realtime through the integration.
Build a chat UI: message list, auto-scroll, and a composer that inserts a row. Stack: Vite + React + TypeScript + Tailwind. Clean up the subscription on unmount. Scope edits to the chat files, src/lib/chat.ts, and the migration.Why it works: subscribing to filtered INSERT events on messages gives live updates without polling, and unsubscribing on unmount avoids leaks.
18. Live dashboard with realtime subscriptions
Make the dashboard update live when data changes, using Supabase realtime.
Do this:
- Subscribe to INSERT, UPDATE, and DELETE on the tasks table (scoped by RLS to the user's rows) and recompute the dashboard counts when a change arrives.
- Debounce recomputation so a burst of changes triggers one refresh, not many.
- Show a subtle "live" indicator, and fall back to a manual refresh if the realtime channel drops.
Requirements:
- Enable realtime on the tasks table through the integration.
- Reuse the get_dashboard_summary RPC to recompute totals instead of pulling all rows.
- Unsubscribe on unmount.
Only edit the dashboard files. Stack: Vite + React + TypeScript + Tailwind.Best for: a metrics view that stays current as rows change, recomputing through an RPC on realtime events instead of refetching everything.
Edge functions & Stripe
Push privileged work into Supabase Edge Functions so secrets and service-role writes never reach the client. For payments, the Stripe webhook — running in an edge function — is the source of truth for plan state, and RLS keeps the client read-only on subscriptions.
19. Edge function for a privileged action
Create a Supabase Edge Function for work that shouldn't run in the client.
Function: [send-invite] — takes an email and a workspace_id, and creates an invite row plus a stubbed email send.
Do this:
- Verify the caller's Supabase JWT inside the function and confirm they're an owner/admin of that workspace before doing anything; return 403 otherwise.
- Use the service-role key (from the function's secrets, never the client) to insert the invite row, bypassing RLS safely after the permission check.
- Return a clear JSON result and proper status codes.
- Stub the actual email behind a helper so it's easy to wire a provider later.
Store the service-role key as a Supabase Edge Function secret — tell me the exact command or dashboard step to set it. On the client, call the function with supabase.functions.invoke. Only add the function and its client caller.Why it works: verifying the JWT and checking permissions before the service-role write keeps a privileged operation safe while still bypassing RLS deliberately.
20. Stripe checkout in an edge function
Add Stripe subscriptions. Create the Checkout Session in a Supabase Edge Function.
Product surface:
- A /pricing page with [Free / Pro / Team] plans and an "Upgrade" button per paid plan.
- Clicking Upgrade calls an edge function that creates a Stripe Checkout Session and returns the URL; the client redirects to it.
Do this:
- Edge function [create-checkout]: verify the caller's JWT, look up or create the Stripe customer for this user, create a subscription Checkout Session for the chosen price ID, and return the session URL.
- Keep the Stripe secret key and price IDs in edge function secrets; the client only sends the plan choice.
- Add a subscriptions table: user_id, stripe_customer_id, stripe_subscription_id, plan, status, current_period_end — RLS lets the user SELECT their own row but never write it.
Tell me which Stripe dashboard values (price IDs, secret key) and which Supabase secrets to set. Only add the pricing page, the function, and the migration. Stack: Vite + React + TypeScript + Tailwind.Best for: starting a subscription safely — the secret key stays server-side in the edge function and the client only picks a plan.
21. Stripe webhook that updates subscriptions
Add the Stripe webhook that keeps the subscriptions table in sync. Make the webhook the source of truth for plan state.
Do this:
- Edge function [stripe-webhook]: read the raw body and verify the Stripe signature with the webhook signing secret before doing anything.
- Handle checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted: upsert the subscriptions row (plan, status, current_period_end, stripe ids) using the service-role key.
- Handle failed payments and cancellations by setting the right status, and make the handler idempotent so a replayed event doesn't corrupt state.
- Never trust the client to set plan state — only this webhook writes the subscriptions row.
Tell me the exact steps to register this endpoint in the Stripe dashboard and which signing secret to store as an edge function secret. Only add the webhook function. Stack unchanged.Why it works: verifying the signature and making the handler idempotent puts plan state where it belongs — in a server-side webhook, not client code that can be spoofed.
22. Gate features by subscription plan
Gate features by the user's Stripe subscription plan stored in Supabase.
Do this:
- Read the current user's subscriptions row (plan, status) via the Supabase client; treat status 'active' or 'trialing' as entitled.
- Add a usePlan() hook exposing the plan and helpers like canUse('feature').
- Gate [advanced analytics and CSV export] behind Pro; show an upgrade prompt with a link to /pricing for users on Free.
- Enforce the limit that matters on the server too: add an RLS or edge-function check so a Free user can't perform the paid action even if they bypass the UI.
Keep the gate logic in one file (src/lib/plan.ts) so it's not scattered. Only add that file and the small UI guards. Stack: Vite + React + TypeScript + Tailwind.Best for: turning a subscription into real access control, with the UI gate backed by a server-side check so it can't be bypassed.
Work through these by section and you have a connected, well-modeled, secured, and monetized Supabase backend — schema and seed, auth, RLS, CRUD and queries, storage, realtime, edge functions, and Stripe — all built inside WebContainer. When a step needs a sharper prompt, reach for the best Bolt.new prompts roundup, or the SaaS dashboard prompts for the screens that sit on top of this data.
Frequently Asked Questions
How does Bolt.new connect to Supabase?
Bolt.new has a built-in Supabase integration you turn on from the toolbar, not in chat. Click Connect Supabase, authorize, and pick or create a project; Bolt then wires the Supabase client and the URL and anon key as environment variables. Because Bolt runs in WebContainer — a browser Node.js runtime with no local database — Supabase provides hosted Postgres your app talks to over the network. In your prompt, name the tables, columns, relationships, and RLS policies in plain language and let Bolt generate the SQL and client code.
Should I mention row-level security in my Bolt prompt?
Always. Supabase exposes tables through a public API, so a table without row-level security is readable and writable by anyone with the anon key. Tell Bolt to enable RLS on every table and describe each policy in plain language — for example, users can only select and update rows where user_id equals their auth.uid(). Naming the policies in the prompt makes Bolt generate the SQL alongside the schema instead of leaving the tables open.
Can Bolt.new create the database schema for me?
Yes. Describe the tables, columns, types, and relationships in plain language and Bolt generates the SQL migration and runs it against your connected Supabase project. Be specific: give each column a type, mark foreign keys, add indexes on columns you filter by, and state defaults like created_at now(). Bolt applies the migration through the integration, so the hosted Postgres schema matches what you described.
Does Bolt.new support Supabase Auth?
Yes. Ask Bolt to use Supabase Auth for email/password, magic link, or OAuth, and it wires the client, the session handling, and protected routes. Name the sign-in methods you want, the redirect behavior for unauthenticated users, and whether a profile row should be created on first sign-in. Pair auth with RLS so that logging in and data access enforce the same user boundary.
How do I use Supabase Storage in Bolt?
Tell Bolt to create a Supabase Storage bucket, whether it is public or private, and the upload rules — allowed file types, max size, and a storage RLS policy scoping files to the owner. Bolt generates the upload component, validates on the client, and stores the returned path in your table. For private buckets, ask it to serve files through signed URLs so access still respects the owner.
Can Bolt build Supabase realtime and edge functions?
Yes. For realtime, ask Bolt to subscribe to Postgres changes on a table so the UI updates live when rows change — good for chat, presence, and live dashboards. For server-side logic, ask for a Supabase Edge Function to handle work you don't want in the client, such as a webhook handler or a privileged write. Because everything runs against hosted Supabase, these work from inside WebContainer without any local server.
How do I wire Stripe subscriptions with Supabase in Bolt?
Ask Bolt to create the Checkout Session in a Supabase Edge Function and handle the Stripe webhook in another edge function that verifies the signature and updates the subscription row with the service-role key. Keep the plan status in a subscriptions table protected by RLS so the client reads it but never writes it. The webhook is the source of truth for plan state, and secrets live in Supabase Edge Function secrets, never in the client.
Why do Bolt prompts waste tokens on Supabase builds?
Every message spends tokens even if the build fails, so vague database prompts are expensive. Pin the frontend stack, name exact tables, columns, and relationships, spell out RLS policies, and scope each edit to named files like src/lib/supabase.ts. Ask Bolt to plan the schema first for a large backend, then build one slice at a time. A precise prompt lands the migration on the first try instead of costing several correction rounds.