Skip to main content

Overview

This quickstart guide will walk you through:
  1. Creating a Supabase project
  2. Setting up a database table
  3. Connecting from your application
  4. Performing CRUD operations
  5. Adding authentication
  6. Securing data with Row Level Security
By the end, you’ll have a working application with authentication and a secure database.
Prefer video? Check out our video tutorials or follow framework-specific guides for Next.js, React, or Flutter.

Step 1: Create a Supabase Project

1

Sign up for Supabase

Navigate to supabase.com/dashboard and sign up for a free account using:
  • GitHub
  • GitLab
  • Bitbucket
  • Email/password
Using a Git provider enables seamless integration with CI/CD pipelines and Vercel deployments.
2

Create a new project

Click New Project and fill in the details:
  • Name: Choose a descriptive name (e.g., “my-app”)
  • Database Password: Use a strong password and save it securely
  • Region: Select the region closest to your users
  • Pricing Plan: Start with the Free tier
Click Create new project and wait 2-3 minutes for provisioning.
Save your database password! You’ll need it for direct database connections. The password cannot be recovered, only reset.
3

Get your API credentials

Once your project is ready:
  1. Go to Project Settings (gear icon in sidebar)
  2. Navigate to the API section
  3. Copy the following values:
    • Project URL (e.g., https://xyzcompany.supabase.co)
    • anon public key (safe to use in browsers)
You’ll use these to connect your application.

Step 2: Create Your First Table

Let’s create a simple todos table to manage tasks.
1

Open SQL Editor

Navigate to SQL Editor in the left sidebar. This is where you can run SQL queries and manage your database schema.
2

Create the table

Click New Query and paste the following SQL:
-- Create todos table
create table todos (
  id bigint generated always as identity primary key,
  user_id uuid references auth.users(id) on delete cascade not null,
  title text not null,
  description text,
  is_complete boolean default false,
  created_at timestamptz default now()
);

-- Enable Row Level Security
alter table todos enable row level security;

-- Create policies
create policy "Users can view their own todos"
  on todos for select
  using (auth.uid() = user_id);

create policy "Users can create their own todos"
  on todos for insert
  with check (auth.uid() = user_id);

create policy "Users can update their own todos"
  on todos for update
  using (auth.uid() = user_id);

create policy "Users can delete their own todos"
  on todos for delete
  using (auth.uid() = user_id);
Click Run or press Cmd/Ctrl + Enter to execute.
3

Verify the table

Navigate to Table Editor in the sidebar. You should see your new todos table with the defined columns.You can manually add test data here, but we’ll insert data programmatically in the next step.
What is Row Level Security (RLS)?RLS is a PostgreSQL feature that restricts which rows users can access. The policies we created ensure users can only see and modify their own todos. This security is enforced at the database level, not in your application code.

Step 3: Install the Supabase Client

Now let’s connect to Supabase from your application.
Install the Supabase JavaScript client:
npm install @supabase/supabase-js
Create a Supabase client (lib/supabase.js or lib/supabase.ts):
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Create a .env.local file with your credentials:
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
The NEXT_PUBLIC_ prefix makes variables available in the browser when using Next.js. For other frameworks, check their documentation on environment variables.

Step 4: Perform CRUD Operations

Now let’s interact with our database.
import { supabase } from './lib/supabase'

// Create a todo
const createTodo = async (title: string, description: string) => {
  const { data, error } = await supabase
    .from('todos')
    .insert({
      title,
      description,
      user_id: (await supabase.auth.getUser()).data.user?.id
    })
    .select()
    .single()
  
  if (error) throw error
  return data
}

// Read todos
const getTodos = async () => {
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .order('created_at', { ascending: false })
  
  if (error) throw error
  return data
}

// Update a todo
const updateTodo = async (id: number, is_complete: boolean) => {
  const { error } = await supabase
    .from('todos')
    .update({ is_complete })
    .eq('id', id)
  
  if (error) throw error
}

// Delete a todo
const deleteTodo = async (id: number) => {
  const { error } = await supabase
    .from('todos')
    .delete()
    .eq('id', id)
  
  if (error) throw error
}
These queries will fail until a user is authenticated because of our Row Level Security policies. Let’s add authentication next.

Step 5: Add Authentication

Supabase provides multiple authentication methods out of the box.

Email/Password Authentication

import { supabase } from './lib/supabase'

// Sign up
const signUp = async (email: string, password: string) => {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  })
  
  if (error) throw error
  return data
}

// Sign in
const signIn = async (email: string, password: string) => {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  })
  
  if (error) throw error
  return data
}

// Sign out
const signOut = async () => {
  const { error } = await supabase.auth.signOut()
  if (error) throw error
}

// Get current user
const getUser = async () => {
  const { data: { user } } = await supabase.auth.getUser()
  return user
}

// Listen to auth changes
supabase.auth.onAuthStateChange((event, session) => {
  console.log(event, session)
  // Update your UI based on auth state
})
Users can sign in with just their email - no password required:
const signInWithMagicLink = async (email: string) => {
  const { error } = await supabase.auth.signInWithOtp({
    email,
    options: {
      emailRedirectTo: 'https://yourapp.com/auth/callback'
    }
  })
  
  if (error) throw error
  // User receives email with magic link
}

Social Authentication

Enable social providers in Authentication > Providers in your dashboard:
// Sign in with Google
const signInWithGoogle = async () => {
  const { error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: 'https://yourapp.com/auth/callback'
    }
  })
  
  if (error) throw error
}

// Also available: github, gitlab, azure, facebook, twitter, discord, and more

Step 6: Subscribe to Realtime Changes

Get instant updates when data changes:
// Subscribe to INSERT events
const subscription = supabase
  .channel('todos')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'todos'
    },
    (payload) => {
      console.log('New todo created:', payload.new)
      // Update your UI with the new todo
    }
  )
  .subscribe()

// Subscribe to all changes
const allChanges = supabase
  .channel('todos')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'todos'
    },
    (payload) => {
      console.log('Change received:', payload)
    }
  )
  .subscribe()

// Cleanup
supabase.removeChannel(subscription)
Enable Realtime on your tableGo to Database > Replication in your dashboard and enable realtime for the todos table.

Step 7: Deploy Your Application

1

Install Supabase Integration

  1. Go to the Supabase Vercel Integration
  2. Click Add Integration
  3. Select your Vercel project and Supabase project
  4. Environment variables are automatically configured
2

Deploy

git push origin main
Vercel automatically deploys your application with the correct environment variables.

Manual Deployment

For other platforms:
  1. Add environment variables to your hosting platform:
    • NEXT_PUBLIC_SUPABASE_URL
    • NEXT_PUBLIC_SUPABASE_ANON_KEY
  2. Configure redirect URLs in Authentication > URL Configuration:
    • Add your production URL to Site URL
    • Add redirect URLs to Redirect URLs
  3. Deploy your application

Next Steps

Congratulations! You’ve built a full-stack application with Supabase. Here’s what to explore next:

Architecture Deep Dive

Learn how all Supabase components work together

Storage

Upload and serve files with automatic optimization

Edge Functions

Add custom server-side logic with TypeScript

Database Functions

Write PostgreSQL functions for complex logic

Common Issues

If you can’t read or write data:
  1. Verify RLS is enabled: ALTER TABLE todos ENABLE ROW LEVEL SECURITY;
  2. Check your policies match your use case
  3. Use the Table Editor to verify policies are active
  4. Test with the SQL Editor using SELECT * FROM todos; while signed in
  • Restart your dev server after adding .env files
  • Check the variable naming (e.g., NEXT_PUBLIC_ prefix for Next.js)
  • Verify the file is in the correct location (project root)
  • Don’t commit .env.local to version control
CORS errors usually mean:
  • Your site URL isn’t configured in Authentication > URL Configuration
  • You’re using the wrong URL (check for http vs https)
  • Your redirect URL isn’t in the allowed list
Generate TypeScript types from your database:
npx supabase gen types typescript --project-id your-project-ref > types/supabase.ts
Then use them in your client:
import { Database } from './types/supabase'

const supabase = createClient<Database>(url, key)

Resources

Build docs developers (and LLMs) love