83  83. Step 9: Set Up Supabase Database

As of Oct 22, 2026 · Official docs: Supabase: Database migrations · supabase db push

Your project ships with migrations: SQL files in supabase/migrations that define the whole database. One command sends them to your Supabase project. They create the tables (profiles, tasks, usage tracking), the security rules, the task-attachments storage bucket for images, and the connection to Stripe.

83.1 Run Database Migrations

Do this after the Supabase CLI is logged in and linked to your project (Step 5a). In Classes 10–12, this is (G) Create the database, after the Supabase, Google, OpenAI and Stripe setup.

  1. Work in the task-app folder, the project root (where supabase/config.toml lives).

  2. Claude runs this command in the terminal for you, after asking your permission. (To run it yourself, type it in VS Code’s terminal and press Enter.)

    npx supabase db push

    npx supabase runs the Supabase CLI, and db push means “send my database changes up to my Supabase project” (db for database, push for upload). It applies every migration that your project doesn’t have yet.

If you see “function uuid_generate_v4() does not exist”

There is a bug in the current code. When running the above command you will probably see the following errors.

$ npx supabase db push
Initialising login role...
Connecting to remote database...
Do you want to push these migrations to the remote database?
 • 0_init_profile.sql
 • 1_init_tasks.sql
 • 2_init_auth.sql
 • 3_init_storage_schema.sql
 • 4_init_usage_tracking.sql
 • 5_init_task_limit_triggers.sql
 • 6_init_account_tier_triggers.sql
 • 7_init_stripe_integration.sql

 [Y/n] y
 
Applying migration 0_init_profile.sql...
NOTICE (42710): extension "uuid-ossp" already exists, skipping

Applying migration 1_init_tasks.sql...
ERROR: function uuid_generate_v4() does not exist (SQLSTATE 42883)
At statement: 0                                                   
-- Tasks table                                                    
create table public.tasks (                                       
  task_id uuid default uuid_generate_v4() primary key,            
                       ^                                          
Try rerunning the command with --debug to troubleshoot the error.

This is caused by the file: supabase/migrations/1_init_tasks.sql

The file contains a call to the function uuid_generate_v4(), which comes from the uuid-ossp extension. On Supabase, that extension is already installed (that’s what the NOTICE above says), but in a separate schema (a folder of database objects) named extensions. The migration calls the function without that schema name, and while db push runs it, the database doesn’t look in extensions, so it reports that the function does not exist.

The simplest fix: in supabase/migrations/1_init_tasks.sql, replace uuid_generate_v4() with gen_random_uuid(). That function is built into PostgreSQL itself (no extension needed) and also creates a random ID. (Writing the full name, extensions.uuid_generate_v4(), would also work.) Save the file and try the npx supabase db push again.

  1. Wait for the migrations to complete without errors.
  2. In the Supabase dashboard, open Table Editor to confirm the tables exist, and Storage to confirm the task-attachments bucket exists.

83.2 Enable Row Level Security (RLS)

The migrations turn on RLS for the profiles, tasks and usage_tracking tables and define these policies:

  • tasks: a signed-in user can read, create, change and delete only the rows whose user_id is their own user ID.
  • profiles: a signed-in user can only read their own profile. There is no policy that lets users create, change or delete profiles. (There are two read policies, “Users can read own profile” and “Users can read own Stripe data”, but both allow the same thing.) A trigger creates the profile when someone signs up, and the Stripe Edge Function changes the subscription plan after a payment.
  • usage_tracking: a signed-in user can only read their own rows. A trigger updates the counts when a task is created.
  • Visitors who aren’t signed in can’t read or change any of these tables, because no policy allows it.
  • Code that uses the Supabase secret key without a signed-in user’s token (like the Stripe Edge Function) bypasses RLS completely.

The storage bucket task-attachments has its own policies: anyone can view the files, but only signed-in users can upload or delete files, and only in their own folder.

Each policy is a create policy statement in the migration files (2_init_auth.sql, 3_init_storage_schema.sql, 4_init_usage_tracking.sql and 7_init_stripe_integration.sql), so you can read them there. The Supabase dashboard also has a Policies page that lists them.

Practice Questions

Easy

Question 1 · Short answer

What is a database migration?

A file of SQL commands that changes the structure of a database in a controlled, repeatable way, for example by creating tables or security policies. task-app’s migrations, in the supabase/migrations/ folder, define its whole database: the schema (the tables and their columns) and the policies.

Question 2 · Write the command

Write the command that sends the project’s migrations to your remote Supabase database.

npx supabase db push

(In this tutorial, Supabase CLI commands start with npx.)

Question 3 · Short answer

According to the page, which folder should you be in when you run that command, and why?

The project’s root (top-level) folder, task-app: the folder that contains supabase/config.toml. The CLI works with that project’s supabase folder, which holds the configuration and the migration files in supabase/migrations/.

Question 4 · True or false

Row Level Security (RLS) lets the database decide, row by row, which data each user may read or change.

True. With RLS turned on, the database checks rules called policies before it returns or changes rows. In task-app, the policies make sure that users can only work with rows whose user_id matches their own user ID.

Question 5 · Multiple choice

Which of these does npx supabase db push not create in your Supabase project?

  1. The profiles, tasks and usage_tracking tables
  2. The security rules (RLS policies) for those tables
  3. The task-attachments storage bucket for task images
  4. Your Supabase project’s publishable and secret API keys

d. The migrations create the tables (a), their security rules (b), the storage bucket (c) and the connection to Stripe. The API keys come with the Supabase project itself, when you create it; you find them under Project Settings > API Keys. That is why you don’t have to create the bucket by hand.

Medium

Question 6 · Predict the output

db push lists eight migrations, from 0_init_profile.sql to 7_init_stripe_integration.sql. Which one is applied first, and why does the order matter?

0_init_profile.sql. Migrations run in order (here, by the number at the start of each file name), because later migrations build on earlier ones. For example, the tasks table created in 1_init_tasks.sql refers to the profiles table, so the profiles must be set up first.

Question 7 · Short answer

During db push you see:

NOTICE (42710): extension "uuid-ossp" already exists, skipping

Is this an error that you need to fix?

No. It is only a notice: the migration asks for the uuid-ossp extension to be installed if it isn’t already, it was already there, so that step was skipped and the migration continued. Real problems are labeled ERROR, like the message that follows it on the page.

Question 8 · Find the bug

The push stops with:

ERROR: function uuid_generate_v4() does not exist (SQLSTATE 42883)

Which file do you change, what do you change, and what do you do next?

In supabase/migrations/1_init_tasks.sql, replace the call uuid_generate_v4() with gen_random_uuid(), a function built into PostgreSQL that also creates a random ID. Save the file and run npx supabase db push again.

Question 9 · Short answer

How can you check that the migrations worked?

First, the command must finish without any ERROR messages. Then open the Table Editor in the Supabase dashboard and confirm that the tables (such as tasks) exist, and open Storage to confirm that the task-attachments bucket exists.

Question 10 · Matching

Match each kind of access with what the RLS policies described on this page allow.

Who What the policies allow
1. A signed-in user A. Bypasses RLS completely
2. A visitor who isn’t signed in B. Can read, create, change and delete their own tasks, but can only read their own profile
3. Code that uses the Supabase secret key, without a user’s token C. Can’t read or change the profiles, tasks or usage_tracking tables

1 → B, 2 → C, 3 → A.

Hard

Question 11 · Scenario

After fixing 1_init_tasks.sql, you run npx supabase db push again. Will 0_init_profile.sql be applied a second time? Explain.

No. Supabase keeps a migration history table in your remote database. Every migration that is applied successfully is recorded there, and later pushes skip the migrations that are already recorded. 0_init_profile.sql succeeded the first time, so the second push only offers the migrations that haven’t been applied yet, starting with 1_init_tasks.sql.

Question 12 · Long answer

Why do projects define their database with migration files instead of having each developer create the tables by clicking around in the Supabase dashboard? Give at least two reasons.

  • Migrations are files, so they are stored in Git with the rest of the code: changes have a history and can be shared and reviewed.
  • Anyone can rebuild exactly the same database with one command (npx supabase db push): a teammate, a new Supabase project, or the production database. That is how you set up the entire task-app database without creating a single table by hand.
  • Changes are applied in a fixed order, which avoids the mistakes that are easy to make when clicking by hand and hard to repeat exactly.

Question 13 · Scenario

Imagine the migrations had created the tasks table but forgot to turn on RLS for it. What risk would that create for task-app’s users?

Without RLS, the database would not check who is asking, so the rule “users can only work with their own tasks” would not be enforced. Other users, and possibly even visitors who aren’t signed in, could read or change everyone’s tasks. Turning on RLS and adding policies is what keeps each user’s data private.

© 2025-2026 Y. Rosenthal. All rights reserved.