🎯

supabase-mcp-integration

🎯Skill

from manutej/crush-mcp-server

VibeIndex|
What it does

supabase-mcp-integration skill from manutej/crush-mcp-server

supabase-mcp-integration

Installation

npm installInstall npm package
npm install @supabase/supabase-js
npm installInstall npm package
npm install -g supabase
πŸ“– Extracted from docs: manutej/crush-mcp-server
6
-
Last UpdatedNov 29, 2025

Skill Details

SKILL.md

Comprehensive Supabase integration covering authentication, database operations, realtime subscriptions, storage, and MCP server patterns for building production-ready backends with PostgreSQL, Auth, and real-time capabilities

Overview

# Supabase MCP Integration

A comprehensive skill for building production-ready applications using Supabase - the open-source Backend-as-a-Service platform built on PostgreSQL. This skill covers authentication, database operations, real-time subscriptions, storage, TypeScript integration, and Row-Level Security patterns.

When to Use This Skill

Use this skill when:

  • Building full-stack web or mobile applications with PostgreSQL backend
  • Implementing authentication (email, OAuth, magic links, MFA) and session management
  • Creating real-time applications (chat, collaboration, live dashboards)
  • Managing file storage with image optimization and CDN delivery
  • Building multi-tenant SaaS applications with fine-grained authorization
  • Migrating from Firebase to SQL-based backend
  • Requiring type-safe database operations with TypeScript
  • Implementing Row-Level Security (RLS) for database authorization
  • Building applications with complex queries, joins, and relationships
  • Setting up instant REST/GraphQL APIs from database schema

Core Concepts

Supabase Platform Architecture

Supabase is an integrated platform built on enterprise-grade open-source components:

Key Components:

  • PostgreSQL Database: Full Postgres with extensions (PostGIS, pg_vector)
  • GoTrue (Auth): JWT-based authentication with multiple providers
  • PostgREST: Auto-generated REST APIs from database schema
  • Realtime: WebSocket server for database changes, broadcast, and presence
  • Storage: S3-compatible file storage with CDN and image optimization
  • Edge Functions: Globally distributed serverless functions (Deno runtime)

Unified Client Library:

```typescript

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

// All features through single client

await supabase.auth.signIn() // Authentication

await supabase.from('users').select() // Database

supabase.channel('room').subscribe() // Realtime

await supabase.storage.from().upload() // Storage

```

Row-Level Security (RLS)

Database-level authorization using PostgreSQL policies:

  • Define access rules directly in the database
  • Automatic enforcement on all queries
  • Integrated with JWT authentication
  • Fine-grained control at row and column level

JWT-Based Authentication

Supabase Auth uses JSON Web Tokens:

  • Issued upon successful authentication
  • Automatically included in database queries
  • Used for RLS policy evaluation
  • Refresh token flow for long sessions

Type Safety

Automatic TypeScript type generation from database schema:

  • Generate types from live database
  • Type-safe queries and mutations
  • Compile-time error detection
  • IDE autocomplete support

Supabase Client Setup

Installation

```bash

# npm

npm install @supabase/supabase-js

# yarn

yarn add @supabase/supabase-js

# pnpm

pnpm add @supabase/supabase-js

# bun

bun add @supabase/supabase-js

```

Environment Configuration

```bash

# .env.local

NEXT_PUBLIC_SUPABASE_URL=https://xyzcompany.supabase.co

NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# For server-side operations (keep secure!)

SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

```

Security Note: Never expose the service_role key in client-side code.

Client Initialization Pattern (Recommended)

```typescript

// lib/supabase.ts

import { createClient, SupabaseClient } from '@supabase/supabase-js'

import { Database } from './database.types'

function validateEnvironment() {

const url = process.env.NEXT_PUBLIC_SUPABASE_URL

const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

if (!url) {

throw new Error('Missing environment variable: NEXT_PUBLIC_SUPABASE_URL')

}

if (!anonKey) {

throw new Error('Missing environment variable: NEXT_PUBLIC_SUPABASE_ANON_KEY')

}

return { url, anonKey }

}

let supabaseInstance: SupabaseClient | null = null

export function getSupabaseClient(): SupabaseClient {

if (!supabaseInstance) {

const { url, anonKey } = validateEnvironment()

supabaseInstance = createClient(url, anonKey, {

auth: {

autoRefreshToken: true,

persistSession: true,

detectSessionInUrl: true

},

global: {

headers: {

'X-Application-Name': 'MyApp'

}

}

})

}

return supabaseInstance

}

// Export singleton instance

export const supabase = getSupabaseClient()

```

Configuration Options

```typescript

const options = {

// Database configuration

db: {

schema: 'public' // Default schema

},

// Authentication configuration

auth: {

autoRefreshToken: true, // Automatically refresh tokens

persistSession: true, // Persist session to localStorage

detectSessionInUrl: true, // Detect session from URL hash

flowType: 'pkce', // Use PKCE flow for OAuth

storage: customStorage, // Custom storage implementation

storageKey: 'sb-auth-token' // Storage key for session

},

// Global configuration

global: {

headers: {

'X-Application-Name': 'my-app',

'apikey': SUPABASE_ANON_KEY

},

fetch: customFetch // Custom fetch implementation

},

// Realtime configuration

realtime: {

params: {

eventsPerSecond: 10

},

timeout: 10000,

heartbeatInterval: 30000

}

}

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, options)

```

Platform-Specific Setup

React Native with AsyncStorage:

```typescript

import AsyncStorage from '@react-native-async-storage/async-storage'

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {

auth: {

storage: AsyncStorage,

autoRefreshToken: true,

persistSession: true,

detectSessionInUrl: false

}

})

```

React Native with Expo SecureStore:

```typescript

import * as SecureStore from 'expo-secure-store'

import { createClient } from '@supabase/supabase-js'

const ExpoSecureStoreAdapter = {

getItem: (key: string) => SecureStore.getItemAsync(key),

setItem: (key: string, value: string) => SecureStore.setItemAsync(key, value),

removeItem: (key: string) => SecureStore.deleteItemAsync(key)

}

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {

auth: {

storage: ExpoSecureStoreAdapter,

autoRefreshToken: true,

persistSession: true

}

})

```

Authentication & Authorization

Email/Password Authentication

Sign Up:

```typescript

const { data, error } = await supabase.auth.signUp({

email: 'user@example.com',

password: 'secure-password',

options: {

data: {

// Additional user metadata

display_name: 'John Doe',

avatar_url: 'https://example.com/avatar.jpg'

},

emailRedirectTo: 'https://yourapp.com/welcome'

}

})

if (error) {

console.error('Signup failed:', error.message)

return

}

console.log('User created:', data.user)

console.log('Session:', data.session)

```

Sign In:

```typescript

const { data, error } = await supabase.auth.signInWithPassword({

email: 'user@example.com',

password: 'secure-password'

})

if (error) {

console.error('Login failed:', error.message)

return

}

console.log('User:', data.user)

console.log('Session token:', data.session?.access_token)

```

Magic Link (Passwordless)

```typescript

const { data, error } = await supabase.auth.signInWithOtp({

email: 'user@example.com',

options: {

emailRedirectTo: 'https://yourapp.com/login',

shouldCreateUser: true

}

})

if (error) {

console.error('Failed to send magic link:', error.message)

return

}

console.log('Magic link sent')

```

One-Time Password (OTP) - Phone

```typescript

// Send OTP

const { data, error } = await supabase.auth.signInWithOtp({

phone: '+1234567890',

options: {

channel: 'sms' // or 'whatsapp'

}

})

// Verify OTP

const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({

phone: '+1234567890',

token: '123456',

type: 'sms'

})

```

OAuth (Social Login)

```typescript

const { data, error } = await supabase.auth.signInWithOAuth({

provider: 'google',

options: {

redirectTo: 'https://yourapp.com/auth/callback',

scopes: 'email profile',

queryParams: {

access_type: 'offline',

prompt: 'consent'

}

}

})

// Supported providers:

// apple, google, github, gitlab, bitbucket, discord, facebook,

// twitter, microsoft, linkedin, notion, slack, spotify, twitch, etc.

```

Session Management

```typescript

// Get current session

const { data: { session }, error } = await supabase.auth.getSession()

if (session) {

console.log('Access token:', session.access_token)

console.log('User:', session.user)

console.log('Expires at:', session.expires_at)

}

// Get current user

const { data: { user }, error } = await supabase.auth.getUser()

// Refresh session

const { data, error } = await supabase.auth.refreshSession()

// Sign out

const { error } = await supabase.auth.signOut()

```

Auth State Changes

```typescript

const { data: { subscription } } = supabase.auth.onAuthStateChange(

(event, session) => {

console.log('Auth event:', event)

switch (event) {

case 'SIGNED_IN':

console.log('User signed in:', session?.user)

break

case 'SIGNED_OUT':

console.log('User signed out')

break

case 'TOKEN_REFRESHED':

console.log('Token refreshed')

break

case 'USER_UPDATED':

console.log('User updated:', session?.user)

break

case 'PASSWORD_RECOVERY':

console.log('Password recovery initiated')

break

}

}

)

// Cleanup

subscription.unsubscribe()

```

User Management

```typescript

// Update user

const { data, error } = await supabase.auth.updateUser({

email: 'newemail@example.com',

password: 'new-password',

data: {

display_name: 'New Name',

avatar_url: 'https://example.com/new-avatar.jpg'

}

})

// Reset password

const { data, error } = await supabase.auth.resetPasswordForEmail(

'user@example.com',

{

redirectTo: 'https://yourapp.com/reset-password'

}

)

// Update password after reset

const { data: updateData, error: updateError } = await supabase.auth.updateUser({

password: 'new-secure-password'

})

```

Multi-Factor Authentication (MFA)

```typescript

// Enroll MFA

const { data: enrollData, error: enrollError } = await supabase.auth.mfa.enroll({

factorType: 'totp',

friendlyName: 'My Phone'

})

// Verify enrollment

const { data: verifyData, error: verifyError } = await supabase.auth.mfa.verify({

factorId: enrollData.id,

code: '123456'

})

// Challenge (during sign-in)

const { data: challengeData, error: challengeError } = await supabase.auth.mfa.challenge({

factorId: 'factor-id'

})

// Verify challenge

const { data, error } = await supabase.auth.mfa.verify({

factorId: 'factor-id',

challengeId: challengeData.id,

code: '123456'

})

```

Database Operations

SELECT Queries

Basic Select:

```typescript

// Select all columns

const { data, error } = await supabase

.from('users')

.select()

// Select specific columns

const { data, error } = await supabase

.from('users')

.select('id, email, created_at')

```

Filtering:

```typescript

// Equal

const { data } = await supabase

.from('users')

.select()

.eq('status', 'active')

// Not equal

const { data } = await supabase

.from('users')

.select()

.neq('role', 'admin')

// Greater than / Less than

const { data } = await supabase

.from('products')

.select()

.gt('price', 100)

.lte('stock', 10)

// In array

const { data } = await supabase

.from('users')

.select()

.in('id', [1, 2, 3, 4, 5])

// Pattern matching

const { data } = await supabase

.from('users')

.select()

.like('email', '%@gmail.com')

// Case-insensitive pattern matching

const { data } = await supabase

.from('products')

.select()

.ilike('name', '%laptop%')

// Full text search

const { data } = await supabase

.from('articles')

.select()

.textSearch('title', 'postgres database')

// Null checks

const { data } = await supabase

.from('users')

.select()

.is('deleted_at', null)

```

Ordering and Pagination:

```typescript

// Order by

const { data } = await supabase

.from('posts')

.select()

.order('created_at', { ascending: false })

// Multiple ordering

const { data } = await supabase

.from('users')

.select()

.order('last_name', { ascending: true })

.order('first_name', { ascending: true })

// Limit results

const { data } = await supabase

.from('posts')

.select()

.limit(10)

// Pagination with range

const { data } = await supabase

.from('posts')

.select()

.range(0, 9) // First 10 items (0-indexed)

```

Joins and Nested Queries:

```typescript

// One-to-many relationship

const { data } = await supabase

.from('users')

.select(`

id,

email,

posts (

id,

title,

created_at

)

`)

// Many-to-many with junction table

const { data } = await supabase

.from('users')

.select(`

id,

email,

user_roles (

role:roles (

id,

name

)

)

`)

// Nested filtering

const { data } = await supabase

.from('users')

.select(`

id,

email,

posts!inner (

id,

title

)

`)

.eq('posts.published', true)

```

Aggregation:

```typescript

// Count

const { count, error } = await supabase

.from('users')

.select('*', { count: 'exact', head: true })

// Count with filtering

const { count } = await supabase

.from('users')

.select('*', { count: 'exact', head: true })

.eq('status', 'active')

```

INSERT Operations

```typescript

// Insert single row

const { data, error } = await supabase

.from('users')

.insert({

email: 'user@example.com',

name: 'John Doe',

age: 30

})

.select() // Return inserted row

// Insert multiple rows

const { data, error } = await supabase

.from('users')

.insert([

{ email: 'user1@example.com', name: 'User One' },

{ email: 'user2@example.com', name: 'User Two' },

{ email: 'user3@example.com', name: 'User Three' }

])

.select()

// Upsert (Insert or Update)

const { data, error } = await supabase

.from('users')

.upsert({

id: 1,

email: 'updated@example.com',

name: 'Updated Name'

}, {

onConflict: 'id' // Conflict column(s)

})

.select()

```

UPDATE Operations

```typescript

// Update with filter

const { data, error } = await supabase

.from('users')

.update({ status: 'inactive' })

.eq('last_login', null)

.select()

// Update single row by ID

const { data, error } = await supabase

.from('users')

.update({ name: 'New Name' })

.eq('id', userId)

.select()

.single()

// Increment value

const { data, error } = await supabase

.from('profiles')

.update({ login_count: supabase.raw('login_count + 1') })

.eq('id', userId)

```

DELETE Operations

```typescript

// Delete with filter

const { error } = await supabase

.from('users')

.delete()

.eq('status', 'banned')

// Delete single row

const { error } = await supabase

.from('posts')

.delete()

.eq('id', postId)

// Soft delete pattern

const { error } = await supabase

.from('users')

.update({ deleted_at: new Date().toISOString() })

.eq('id', userId)

```

RPC (Remote Procedure Calls)

```typescript

// Call function without parameters

const { data, error } = await supabase

.rpc('get_user_count')

// Call function with parameters

const { data, error } = await supabase

.rpc('calculate_discount', {

product_id: 123,

user_id: 456

})

```

Realtime Subscriptions

Database Change Subscriptions

```typescript

// Listen to all changes

const channel = supabase

.channel('db-changes')

.on(

'postgres_changes',

{

event: '*', // All events: INSERT, UPDATE, DELETE

schema: 'public',

table: 'posts'

},

(payload) => {

console.log('Change received:', payload)

console.log('Event type:', payload.eventType)

console.log('New data:', payload.new)

console.log('Old data:', payload.old)

}

)

.subscribe()

// Cleanup

channel.unsubscribe()

```

Listen to Specific Events:

```typescript

// INSERT only

const insertChannel = supabase

.channel('post-inserts')

.on(

'postgres_changes',

{

event: 'INSERT',

schema: 'public',

table: 'posts'

},

(payload) => {

console.log('New post created:', payload.new)

}

)

.subscribe()

// UPDATE only

const updateChannel = supabase

.channel('post-updates')

.on(

'postgres_changes',

{

event: 'UPDATE',

schema: 'public',

table: 'posts'

},

(payload) => {

console.log('Post updated:', payload.new)

}

)

.subscribe()

// Filter changes for specific rows

const channel = supabase

.channel('user-posts')

.on(

'postgres_changes',

{

event: '*',

schema: 'public',

table: 'posts',

filter: user_id=eq.${userId}

},

(payload) => {

console.log('User post changed:', payload)

}

)

.subscribe()

```

Broadcast Messages

```typescript

// Send broadcast

const channel = supabase.channel('room-1')

channel.subscribe((status) => {

if (status === 'SUBSCRIBED') {

channel.send({

type: 'broadcast',

event: 'cursor-move',

payload: { x: 100, y: 200, user: 'Alice' }

})

}

})

// Receive broadcast

const channel = supabase

.channel('room-1')

.on('broadcast', { event: 'cursor-move' }, (payload) => {

console.log('Cursor moved:', payload)

})

.subscribe()

```

Presence Tracking

```typescript

// Track user presence

const channel = supabase.channel('online-users')

// Set initial state

channel.subscribe(async (status) => {

if (status === 'SUBSCRIBED') {

await channel.track({

user: 'user-1',

online_at: new Date().toISOString(),

status: 'online'

})

}

})

// Listen to presence changes

channel.on('presence', { event: 'sync' }, () => {

const state = channel.presenceState()

console.log('Online users:', state)

})

channel.on('presence', { event: 'join' }, ({ newPresences }) => {

console.log('Users joined:', newPresences)

})

channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {

console.log('Users left:', leftPresences)

})

// Cleanup

channel.unsubscribe()

```

Storage Operations

Bucket Management

```typescript

// List buckets

const { data: buckets, error } = await supabase

.storage

.listBuckets()

// Create bucket

const { data, error } = await supabase

.storage

.createBucket('avatars', {

public: false, // Private bucket

fileSizeLimit: 1048576, // 1MB limit

allowedMimeTypes: ['image/png', 'image/jpeg']

})

// Update bucket

const { data, error } = await supabase

.storage

.updateBucket('avatars', {

public: true

})

// Delete bucket

const { data, error } = await supabase

.storage

.deleteBucket('avatars')

```

File Upload

```typescript

// Standard upload

const file = event.target.files[0]

const filePath = ${userId}/${Date.now()}-${file.name}

const { data, error } = await supabase

.storage

.from('avatars')

.upload(filePath, file, {

cacheControl: '3600',

upsert: false

})

// Upload with progress tracking

const { data, error } = await supabase

.storage

.from('videos')

.upload(filePath, file, {

onUploadProgress: (progress) => {

const percent = (progress.loaded / progress.total) * 100

console.log(Upload progress: ${percent.toFixed(2)}%)

}

})

```

File Download and URLs

```typescript

// Download file

const { data, error } = await supabase

.storage

.from('avatars')

.download('path/to/file.jpg')

// Get public URL (for public buckets)

const { data } = supabase

.storage

.from('avatars')

.getPublicUrl('path/to/file.jpg')

// Create signed URL (for private buckets)

const { data, error } = await supabase

.storage

.from('private-files')

.createSignedUrl('path/to/file.pdf', 60) // Expires in 60 seconds

```

Image Transformation

```typescript

const { data } = supabase

.storage

.from('avatars')

.getPublicUrl('user-avatar.jpg', {

transform: {

width: 200,

height: 200,

resize: 'cover', // or 'contain', 'fill'

quality: 80,

format: 'webp'

}

})

```

File Management

```typescript

// List files

const { data, error } = await supabase

.storage

.from('avatars')

.list('user-123', {

limit: 100,

offset: 0,

sortBy: { column: 'name', order: 'asc' }

})

// Delete files

const { data, error } = await supabase

.storage

.from('avatars')

.remove(['path/to/file1.jpg', 'path/to/file2.jpg'])

// Move file

const { data, error } = await supabase

.storage

.from('avatars')

.move('old/path/file.jpg', 'new/path/file.jpg')

// Copy file

const { data, error } = await supabase

.storage

.from('avatars')

.copy('source/file.jpg', 'destination/file.jpg')

```

TypeScript Integration

Generate Database Types

```bash

# Install Supabase CLI

npm install -g supabase

# Login

supabase login

# Generate types

supabase gen types typescript --project-id YOUR_PROJECT_ID > database.types.ts

```

Use Generated Types

```typescript

import { createClient } from '@supabase/supabase-js'

import { Database } from './database.types'

// Create typed client

const supabase = createClient(

process.env.SUPABASE_URL!,

process.env.SUPABASE_ANON_KEY!

)

// Type-safe queries

const { data, error } = await supabase

.from('users')

.select('id, email, created_at')

.eq('id', userId)

// data is typed as:

// Array<{ id: string; email: string; created_at: string }> | null

// Type-safe inserts

const { data, error } = await supabase

.from('posts')

.insert({

title: 'My Post',

content: 'Content here',

user_id: userId,

published: true

})

.select()

```

Helper Types

```typescript

import { Database } from './database.types'

// Get table row type

type User = Database['public']['Tables']['users']['Row']

// Get insert type

type NewUser = Database['public']['Tables']['users']['Insert']

// Get update type

type UserUpdate = Database['public']['Tables']['users']['Update']

// Get enum type

type UserRole = Database['public']['Enums']['user_role']

// Use in functions

function createUser(user: NewUser): Promise {

// Implementation

}

```

Row-Level Security (RLS)

Enable RLS

```sql

-- Enable RLS on a table

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

```

Common RLS Patterns

Public Read Access:

```sql

CREATE POLICY "Public profiles are visible to everyone"

ON profiles

FOR SELECT

TO anon, authenticated

USING (true);

```

User Can Only See Own Data:

```sql

CREATE POLICY "Users can only see own data"

ON posts

FOR SELECT

TO authenticated

USING (auth.uid() = user_id);

```

User Can Only Modify Own Data:

```sql

CREATE POLICY "Users can insert own posts"

ON posts

FOR INSERT

TO authenticated

WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can update own posts"

ON posts

FOR UPDATE

TO authenticated

USING (auth.uid() = user_id)

WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Users can delete own posts"

ON posts

FOR DELETE

TO authenticated

USING (auth.uid() = user_id);

```

Multi-Tenant Pattern:

```sql

CREATE POLICY "Users can only see data from own organization"

ON documents

FOR SELECT

TO authenticated

USING (

organization_id IN (

SELECT organization_id

FROM user_organizations

WHERE user_id = auth.uid()

)

);

```

Role-Based Access Control:

```sql

CREATE POLICY "Admin can see all users"

ON users

FOR SELECT

TO authenticated

USING (

auth.jwt() ->> 'role' = 'admin'

OR auth.uid() = id

);

```

Best Practices

Client Initialization

Use Singleton Pattern:

  • Create single client instance and reuse across app
  • Avoid creating new clients on every request
  • Store in module-level variable or context

Error Handling

Always Check Errors:

```typescript

const { data, error } = await supabase

.from('users')

.select()

if (error) {

console.error('Error fetching users:', error.message)

// Handle error appropriately

return

}

// Use data safely

console.log(data)

```

Use throwOnError() for Promise Rejection:

```typescript

try {

const { data } = await supabase

.from('users')

.insert({ name: 'John' })

.throwOnError()

console.log('User created:', data)

} catch (error) {

console.error('Failed to create user:', error)

}

```

Security

Never Expose Service Role Key:

  • Use anon key in client-side code
  • Use service_role key only in server-side code
  • Keep service role key in server environment variables

Always Enable RLS:

  • Enable RLS on all tables
  • Create appropriate policies for each table
  • Test policies thoroughly

Validate User Input:

  • Never trust client-side data
  • Use database constraints and validations
  • Validate in both client and database

Performance

Use Select Wisely:

```typescript

// Bad: Fetch all columns

const { data } = await supabase.from('users').select()

// Good: Only fetch needed columns

const { data } = await supabase.from('users').select('id, email')

```

Use Pagination:

```typescript

// Bad: Fetch all rows

const { data } = await supabase.from('posts').select()

// Good: Paginate results

const { data } = await supabase

.from('posts')

.select()

.range(0, 9)

.order('created_at', { ascending: false })

```

Index Database Columns:

  • Add indexes for frequently queried columns
  • Index foreign keys
  • Use composite indexes for multi-column queries

Connection Management

Reuse Client Instance:

  • Don't create new client for each request
  • Use singleton pattern for client initialization
  • Consider connection pooling for server-side

Clean Up Subscriptions:

```typescript

useEffect(() => {

const channel = supabase.channel('room-1')

channel.subscribe(/ ... /)

return () => {

channel.unsubscribe()

}

}, [])

```

Common Patterns & Workflows

User Authentication Flow

```typescript

// 1. Sign up

const { data: signUpData, error: signUpError } = await supabase.auth.signUp({

email: 'user@example.com',

password: 'secure-password'

})

// 2. Listen to auth state

supabase.auth.onAuthStateChange((event, session) => {

if (event === 'SIGNED_IN') {

// Redirect to dashboard

}

})

// 3. Protected route check

const { data: { session } } = await supabase.auth.getSession()

if (!session) {

// Redirect to login

}

// 4. Sign out

await supabase.auth.signOut()

```

CRUD with RLS

```typescript

// Enable RLS on table

/*

ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can CRUD own todos"

ON todos

FOR ALL

TO authenticated

USING (auth.uid() = user_id)

WITH CHECK (auth.uid() = user_id);

*/

// Create

const { data, error } = await supabase

.from('todos')

.insert({

user_id: session.user.id,

title: 'My Todo',

completed: false

})

.select()

// Read (only user's own todos due to RLS)

const { data, error } = await supabase

.from('todos')

.select()

// Update

const { data, error } = await supabase

.from('todos')

.update({ completed: true })

.eq('id', todoId)

// Delete

const { error } = await supabase

.from('todos')

.delete()

.eq('id', todoId)

```

Real-Time Chat Implementation

```typescript

import { useEffect, useState } from 'react'

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

function Chat({ roomId, userId }) {

const [messages, setMessages] = useState([])

useEffect(() => {

// Fetch existing messages

const fetchMessages = async () => {

const { data } = await supabase

.from('messages')

.select()

.eq('room_id', roomId)

.order('created_at', { ascending: true })

if (data) setMessages(data)

}

fetchMessages()

// Subscribe to new messages

const channel = supabase

.channel(room-${roomId})

.on(

'postgres_changes',

{

event: 'INSERT',

schema: 'public',

table: 'messages',

filter: room_id=eq.${roomId}

},

(payload) => {

setMessages((prev) => [...prev, payload.new])

}

)

.subscribe()

return () => {

channel.unsubscribe()

}

}, [roomId])

const sendMessage = async (content: string) => {

await supabase.from('messages').insert({

room_id: roomId,

user_id: userId,

content

})

}

return (

{messages.map((msg) => (

{msg.content}

))}

)

}

```

File Upload with Progress

```typescript

async function uploadAvatar(file: File, userId: string) {

const filePath = ${userId}/${Date.now()}-${file.name}

const { data, error } = await supabase.storage

.from('avatars')

.upload(filePath, file, {

cacheControl: '3600',

upsert: false,

onUploadProgress: (progress) => {

const percent = (progress.loaded / progress.total) * 100

console.log(Upload: ${percent.toFixed(2)}%)

}

})

if (error) {

console.error('Upload failed:', error.message)

return null

}

// Get public URL

const { data: urlData } = supabase.storage

.from('avatars')

.getPublicUrl(filePath)

// Update user profile with avatar URL

await supabase

.from('profiles')

.update({ avatar_url: urlData.publicUrl })

.eq('id', userId)

return urlData.publicUrl

}

```

Troubleshooting

Common Issues

RLS Blocking Queries:

  • Check if RLS is enabled: ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
  • Verify policies exist and match your use case
  • Test policies with different user contexts
  • Use USING clause for SELECT/UPDATE/DELETE
  • Use WITH CHECK clause for INSERT/UPDATE

Auth Session Not Persisting:

  • Ensure persistSession: true in config
  • Check if storage (localStorage) is available
  • Verify cookies are not blocked
  • Check if third-party cookies are enabled (for OAuth)

Realtime Not Working:

  • Enable realtime on table in Supabase dashboard
  • Check if RLS policies allow subscriptions
  • Verify channel subscription is successful
  • Check network/firewall blocking WebSockets

Type Generation Errors:

  • Ensure Supabase CLI is installed and updated
  • Verify project ID is correct
  • Check network connectivity to Supabase
  • Try regenerating types with --debug flag

Debug RLS Policies

```sql

-- Test policy as specific user

SET request.jwt.claims.sub = 'user-uuid-here';

-- Run query to see what's visible

SELECT * FROM posts;

-- Reset to admin

RESET request.jwt.claims.sub;

```

Performance Issues

Slow Queries:

  • Add indexes on frequently queried columns
  • Use EXPLAIN ANALYZE to analyze query plan
  • Avoid fetching unnecessary columns
  • Use pagination for large datasets

Too Many Connections:

  • Use connection pooling
  • Reuse client instance
  • Close unused subscriptions
  • Consider using Edge Functions for server-side logic

Production Deployment

Environment Configuration

```bash

# Production .env

NEXT_PUBLIC_SUPABASE_URL=https://prod-project.supabase.co

NEXT_PUBLIC_SUPABASE_ANON_KEY=prod-anon-key

SUPABASE_SERVICE_ROLE_KEY=prod-service-role-key

# Staging .env

NEXT_PUBLIC_SUPABASE_URL=https://staging-project.supabase.co

NEXT_PUBLIC_SUPABASE_ANON_KEY=staging-anon-key

SUPABASE_SERVICE_ROLE_KEY=staging-service-role-key

```

Database Migrations

Use Supabase CLI for schema migrations:

```bash

# Initialize migrations

supabase migration new create_posts_table

# Apply migrations

supabase db push

# Generate migration from changes

supabase db diff -f create_users_table

```

Monitoring

  • Enable Supabase Dashboard monitoring
  • Set up alerts for errors and performance issues
  • Monitor database connections
  • Track API usage and quotas
  • Set up logging for auth events

Backup Strategy

  • Enable automatic backups in Supabase dashboard
  • Configure point-in-time recovery
  • Test restoration procedures
  • Export schema and data regularly
  • Store backups in separate location

Scaling Considerations

  • Upgrade Supabase plan for higher limits
  • Use database connection pooling
  • Implement caching (Redis, etc.)
  • Consider read replicas for heavy read loads
  • Use Edge Functions for heavy compute
  • Optimize database indexes
  • Monitor and optimize slow queries

---

Skill Version: 1.0.0

Last Updated: October 2025

Skill Category: Backend-as-a-Service, Database Integration, Authentication, Real-time

Compatible With: React, Next.js, Vue, Angular, React Native, Flutter, Node.js, Deno