Skip to main content

Supabase Setup

Configure your Supabase database to store customer and user data, synchronized automatically from Stripe.

Prerequisites

  • A Supabase account (create one at supabase.com)
  • A Supabase project created
  • Basic understanding of PostgreSQL

Step 1: Get Your Supabase Credentials

Navigate to your project settings in the Supabase Dashboard.

You'll need three pieces of information:

  • Project URL: Your unique Supabase project URL
  • Anon/Public Key: For client-side operations
  • Service Role Key: For server-side operations (keep secret!)

Step 2: Configure Environment Variables

Add your Supabase credentials to your environment configuration:

1# Supabase Configuration
2NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
3NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
4SUPABASE_SERVICE_ROLE_KEY=your_service_role_key

⚠️ Security: Never expose your service role key in client-side code. Only use it in server-side API routes.

Step 3: Create Database Tables

Set up the required database schema for customer and user management. Run these SQL commands in the Supabase SQL Editor.

Customers Table

1-- Create end_customers table (end_ prefix = founder's end-user data)
2CREATE TABLE end_customers (
3  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
4  stripe_customer_id TEXT UNIQUE NOT NULL,
5  email TEXT NOT NULL,
6  name TEXT,
7  phone TEXT,
8  metadata JSONB DEFAULT '{}'::jsonb,
9  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
10  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
11);
12
13-- Create index for faster lookups
14CREATE INDEX idx_end_customers_stripe_id ON end_customers(stripe_customer_id);
15CREATE INDEX idx_end_customers_email ON end_customers(email);
16
17-- Enable Row Level Security
18ALTER TABLE end_customers ENABLE ROW LEVEL SECURITY;

Users Table

1-- Create users table (links to Supabase Auth)
2CREATE TABLE users (
3  id UUID PRIMARY KEY REFERENCES auth.users(id),
4  customer_id UUID REFERENCES end_customers(id),
5  full_name TEXT,
6  avatar_url TEXT,
7  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
8  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
9);
10
11-- Enable Row Level Security
12ALTER TABLE users ENABLE ROW LEVEL SECURITY;
13
14-- Create policy to allow users to view their own data
15CREATE POLICY "Users can view own data" ON users
16  FOR SELECT
17  USING (auth.uid() = id);
18
19-- Create policy to allow users to update their own data
20CREATE POLICY "Users can update own data" ON users
21  FOR UPDATE
22  USING (auth.uid() = id);

Payments Table

1-- Create payments table
2CREATE TABLE payments (
3  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
4  customer_id UUID REFERENCES end_customers(id) ON DELETE CASCADE,
5  stripe_payment_intent_id TEXT UNIQUE NOT NULL,
6  amount INTEGER NOT NULL,
7  currency TEXT NOT NULL DEFAULT 'usd',
8  status TEXT NOT NULL,
9  description TEXT,
10  metadata JSONB DEFAULT '{}'::jsonb,
11  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
12);
13
14-- Create indexes
15CREATE INDEX idx_payments_customer_id ON payments(customer_id);
16CREATE INDEX idx_payments_stripe_id ON payments(stripe_payment_intent_id);
17
18-- Enable Row Level Security
19ALTER TABLE payments ENABLE ROW LEVEL SECURITY;

Subscriptions Table

1-- Create subscriptions table
2CREATE TABLE subscriptions (
3  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
4  customer_id UUID REFERENCES end_customers(id) ON DELETE CASCADE,
5  stripe_subscription_id TEXT UNIQUE NOT NULL,
6  status TEXT NOT NULL,
7  price_id TEXT NOT NULL,
8  quantity INTEGER DEFAULT 1,
9  cancel_at_period_end BOOLEAN DEFAULT FALSE,
10  current_period_start TIMESTAMP WITH TIME ZONE,
11  current_period_end TIMESTAMP WITH TIME ZONE,
12  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
13  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
14);
15
16-- Create indexes
17CREATE INDEX idx_subscriptions_customer_id ON subscriptions(customer_id);
18CREATE INDEX idx_subscriptions_stripe_id ON subscriptions(stripe_subscription_id);
19CREATE INDEX idx_subscriptions_status ON subscriptions(status);
20
21-- Enable Row Level Security
22ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;

Step 4: Set Up Row Level Security Policies

Configure RLS policies to control data access:

1-- Allow service role to do everything
2CREATE POLICY "Service role has full access" ON end_customers
3  FOR ALL
4  USING (auth.jwt()->>'role' = 'service_role');
5
6-- Allow authenticated users to view their own customer data
7CREATE POLICY "Users can view own customer data" ON end_customers
8  FOR SELECT
9  USING (
10    auth.uid() IN (
11      SELECT id FROM users WHERE customer_id = end_customers.id
12    )
13  );
14
15-- Similar policies for payments
16CREATE POLICY "Service role has full access" ON payments
17  FOR ALL
18  USING (auth.jwt()->>'role' = 'service_role');
19
20CREATE POLICY "Users can view own payments" ON payments
21  FOR SELECT
22  USING (
23    customer_id IN (
24      SELECT customer_id FROM users WHERE id = auth.uid()
25    )
26  );
27
28-- Similar policies for subscriptions
29CREATE POLICY "Service role has full access" ON subscriptions
30  FOR ALL
31  USING (auth.jwt()->>'role' = 'service_role');
32
33CREATE POLICY "Users can view own subscriptions" ON subscriptions
34  FOR SELECT
35  USING (
36    customer_id IN (
37      SELECT customer_id FROM users WHERE id = auth.uid()
38    )
39  );

Step 5: Create Database Functions

Add helper functions for common operations:

1-- Function to update updated_at timestamp
2CREATE OR REPLACE FUNCTION update_updated_at_column()
3RETURNS TRIGGER AS $$
4BEGIN
5  NEW.updated_at = NOW();
6  RETURN NEW;
7END;
8$$ LANGUAGE plpgsql;
9
10-- Create triggers
11CREATE TRIGGER update_end_customers_updated_at
12  BEFORE UPDATE ON end_customers
13  FOR EACH ROW
14  EXECUTE FUNCTION update_updated_at_column();
15
16CREATE TRIGGER update_users_updated_at
17  BEFORE UPDATE ON users
18  FOR EACH ROW
19  EXECUTE FUNCTION update_updated_at_column();
20
21CREATE TRIGGER update_subscriptions_updated_at
22  BEFORE UPDATE ON subscriptions
23  FOR EACH ROW
24  EXECUTE FUNCTION update_updated_at_column();

Step 6: Test the Connection

Verify your Supabase connection with a simple query:

1import { supabase } from '@/lib/supabase/client';
2
3async function testSupabaseConnection() {
4  try {
5    const { data, error } = await supabase
6      .from('end_customers')
7      .select('count')
8      .limit(1);
9    
10    if (error) throw error;
11    
12    console.log('✓ Supabase connection successful');
13    return true;
14  } catch (error) {
15    console.error('✗ Supabase connection failed:', error);
16    return false;
17  }
18}

Step 7: Enable Realtime (Optional)

Enable realtime subscriptions for live data updates:

1-- Enable realtime for end_customers table
2ALTER PUBLICATION supabase_realtime ADD TABLE end_customers;
3
4-- Enable realtime for payments table
5ALTER PUBLICATION supabase_realtime ADD TABLE payments;
6
7-- Enable realtime for subscriptions table
8ALTER PUBLICATION supabase_realtime ADD TABLE subscriptions;

Best Practices

  • Use RLS Policies: Always enable and configure Row Level Security for data protection
  • Index Frequently Queried Columns: Add indexes on foreign keys and commonly searched fields
  • Use Timestamps: Track created_at and updated_at for all records
  • Store Metadata as JSONB: Use JSONB for flexible data storage without schema changes
  • Regular Backups: Enable automatic backups in Supabase project settings

💡 Pro Tip

Use Supabase's database migrations feature to version control your schema changes. This makes it easy to deploy changes across environments.

Next Steps