supabase-expert

zacharyr0th's avatarfrom zacharyr0th

Advanced Supabase integration specialist for Auth, Database (PostgreSQL/RLS), Storage, Realtime, Edge Functions, and AI/Vector features. Use when implementing Supabase features, debugging Supabase issues, setting up RLS policies, creating database schemas, building auth flows, optimizing Supabase queries, migrating to Supabase, or architecting Supabase-based applications. Invoke for Supabase client setup, type generation, migration creation, performance tuning, security audits, or Supabase best practices. Handles Next.js, React, Vue, Svelte, and server-side integrations.

1stars🔀0forks📁View on GitHub🕐Updated Nov 7, 2025

When & Why to Use This Skill

The Supabase Expert skill is a production-grade integration specialist designed to accelerate backend development and architecture. It provides comprehensive guidance on PostgreSQL database management, Row Level Security (RLS), secure Authentication flows, and Realtime data synchronization. By leveraging deep technical patterns and framework-specific integrations for Next.js, React, and Vue, it ensures that applications are secure, scalable, and optimized for performance. This skill is essential for developers looking to implement advanced features like Edge Functions and AI-powered vector searches while maintaining industry-standard security audits and best practices.

Use Cases

  • Architecting multi-tenant SaaS applications using advanced Row Level Security (RLS) policies and organization-based access control.
  • Implementing secure and complex authentication flows, including Social OAuth, Multi-Factor Authentication (MFA), and server-side session management in Next.js App Router.
  • Building AI-native applications by integrating pgvector for semantic search, vector embeddings, and Retrieval-Augmented Generation (RAG) workflows.
  • Developing and deploying serverless Edge Functions for backend logic such as payment processing with Stripe or automated email triggers.
  • Optimizing database performance through query tuning, effective indexing strategies, and configuring connection pooling for serverless environments.
  • Managing seamless migrations from legacy platforms like Firebase or Parse to a PostgreSQL-based Supabase infrastructure.
namesupabase-expert
descriptionAdvanced Supabase integration specialist for Auth, Database (PostgreSQL/RLS), Storage, Realtime, Edge Functions, and AI/Vector features. Use when implementing Supabase features, debugging Supabase issues, setting up RLS policies, creating database schemas, building auth flows, optimizing Supabase queries, migrating to Supabase, or architecting Supabase-based applications. Invoke for Supabase client setup, type generation, migration creation, performance tuning, security audits, or Supabase best practices. Handles Next.js, React, Vue, Svelte, and server-side integrations.
allowed-toolsRead, Write, Edit, Bash, Grep, Glob
modelsonnet

Supabase Expert Skill - Advanced Implementation Guide

Purpose

This is a comprehensive, production-grade skill for working with Supabase across all aspects of modern application development. It provides:

  • Deep Technical Expertise: Advanced patterns for complex use cases
  • Framework Integration: Specific implementations for Next.js, React, Vue, Svelte
  • Production Readiness: Security audits, performance optimization, error handling
  • Architecture Guidance: Multi-tenancy, scaling strategies, migration patterns
  • Real-world Solutions: Battle-tested patterns from production applications

This skill leverages the complete Supabase documentation (2,190 pages) to provide accurate, up-to-date guidance across:

  • Authentication & Authorization (30+ auth methods)
  • PostgreSQL Database & RLS (Advanced query optimization)
  • Storage & CDN (File management at scale)
  • Realtime (WebSocket subscriptions, presence, broadcast)
  • Edge Functions (Deno runtime, serverless patterns)
  • AI/Vector (Embeddings, semantic search, RAG)

When to Use

Core Implementation Tasks

  • Setting up Supabase client configuration and TypeScript types
  • Implementing authentication (social login, magic links, SSO, MFA, anonymous auth)
  • Creating PostgreSQL schemas with Row Level Security (RLS)
  • Building realtime features with Supabase Realtime
  • Implementing file storage with Supabase Storage
  • Creating or debugging Edge Functions
  • Working with vector embeddings and AI features
  • Setting up local development with Supabase CLI
  • Creating and managing database migrations

Advanced & Production Tasks

  • Troubleshooting Supabase connection or query issues
  • Optimizing Supabase queries and performance
  • Implementing multi-tenancy with RLS
  • Security audits and hardening
  • Migration from Firebase, Parse, or other BaaS
  • Architecting scalable Supabase applications
  • Connection pooling and serverless optimization
  • Implementing complex authorization patterns
  • Setting up CI/CD with Supabase
  • Monitoring and observability setup

Framework-Specific Integration

  • Next.js App Router / Pages Router integration
  • React with context and hooks
  • Vue 3 with Composition API
  • Svelte/SvelteKit integration
  • Server-side rendering (SSR) with Supabase
  • Static site generation (SSG) patterns

Documentation Access & Search Strategy

Documentation Location

Base Path: /Users/zach/Documents/cc-skills/docs/supabase/

Organized Structure

  • guides/auth/ - Authentication (30+ files)
  • guides/database/ - PostgreSQL, RLS, migrations, extensions (35+ files)
  • guides/storage/ - File storage and CDN
  • guides/realtime/ - Real-time subscriptions
  • guides/functions/ - Edge Functions (35+ files)
  • guides/ai/ - Vector embeddings and AI features (18+ files)
  • guides/cli/ - Supabase CLI and local development
  • guides/platform/ - Project management and deployment
  • guides/security/ - Security best practices
  • guides/deployment/ - Production deployment patterns
  • reference/ - Complete API reference (1,583 files)

Advanced Search Strategy

When a request comes in, use a multi-stage search approach:

Stage 1: Broad Category Search

# Identify relevant category
grep -r "keyword" /Users/zach/Documents/cc-skills/docs/supabase/guides/ -l | head -10

Stage 2: Targeted Deep Search

# Search within specific category with context
grep -r -B 2 -A 5 "specific pattern" /Users/zach/Documents/cc-skills/docs/supabase/guides/[category]/ --include="*.txt"

Stage 3: Cross-Reference Search

# Find related documentation
grep -r "related_term_1\|related_term_2\|related_term_3" /Users/zach/Documents/cc-skills/docs/supabase/ -l

Stage 4: API Reference Search

# Search API reference for specific methods
grep -r "method_name" /Users/zach/Documents/cc-skills/docs/supabase/reference/ -l

Documentation Reading Priority

  1. Guides - For conceptual understanding and best practices
  2. Reference - For specific API signatures and parameters
  3. Cross-reference - Check related topics for complete context

Enhanced Process Framework

1. Deep Requirement Analysis

Before providing any solution, analyze:

Technical Requirements:

  • What Supabase feature is needed?
  • What's the user's framework/environment?
  • What's the scale/performance requirements?
  • What are the security considerations?

Context Detection:

# Check if project uses Next.js
[ -f "next.config.js" ] && echo "Next.js detected"

# Check if TypeScript
[ -f "tsconfig.json" ] && echo "TypeScript project"

# Check existing Supabase setup
grep -r "createClient" . --include="*.{ts,js,tsx,jsx}" | head -5

Existing Code Analysis:

  • Search for existing Supabase client setup
  • Identify current patterns being used
  • Check for potential conflicts or improvements

2. Comprehensive Documentation Search

Execute multi-stage search strategy:

# Example: Searching for auth implementation
# Stage 1: Find all auth-related docs
AUTH_DOCS=$(grep -r "authentication\|sign.*in\|auth\..*" /Users/zach/Documents/cc-skills/docs/supabase/guides/auth/ -l)

# Stage 2: Narrow to specific method (e.g., Google OAuth)
OAUTH_DOCS=$(echo "$AUTH_DOCS" | xargs grep -l "google\|oauth")

# Stage 3: Read the most relevant docs
# (Read top 3 most relevant files)

3. Context-Aware Implementation

Provide implementations that match the user's context:

For Next.js App Router:

  • Server Components patterns
  • Server Actions integration
  • Middleware for auth
  • Cookie-based session management

For Next.js Pages Router:

  • API routes patterns
  • getServerSideProps integration
  • Client-side auth hooks

For Client-Only React:

  • Context providers
  • Custom hooks
  • Local state management

For Server-Side (Node/Deno/Bun):

  • Service role patterns
  • Connection pooling
  • Background jobs

4. Production-Grade Implementation

Every code example should include:

Complete TypeScript typesComprehensive error handlingLoading statesEdge cases handledPerformance optimizationsSecurity considerationsTesting examplesMonitoring/logging hooks

5. Validation & Testing Guidance

Provide:

  • Unit test examples
  • Integration test examples
  • E2E test scenarios
  • RLS policy testing
  • Performance benchmarking
  • Security audit checklist

Advanced Implementation Patterns

Pattern Library Location

.claude/skills/supabase-expert/patterns/

Pattern 1: Advanced RLS with Multi-Tenancy

Scenario: Multi-tenant SaaS with organization-based access control

Search Strategy:

grep -r "multi.*tenant\|organization\|team.*access" /Users/zach/Documents/cc-skills/docs/supabase/guides/ -l
grep -r "row.*level.*security.*tenant" /Users/zach/Documents/cc-skills/docs/supabase/guides/database/ --include="*.txt" -A 10

Implementation:

-- Organization table
CREATE TABLE organizations (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Membership table with roles
CREATE TABLE organization_members (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(organization_id, user_id)
);

-- Projects table (tenant data)
CREATE TABLE projects (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  created_by UUID REFERENCES auth.users(id),
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Enable RLS on all tables
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

-- Helper function to check membership
CREATE OR REPLACE FUNCTION is_organization_member(org_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
  RETURN EXISTS (
    SELECT 1 FROM organization_members
    WHERE organization_id = org_id
    AND user_id = auth.uid()
  );
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Helper function to check role
CREATE OR REPLACE FUNCTION get_user_role(org_id UUID)
RETURNS TEXT AS $$
BEGIN
  RETURN (
    SELECT role FROM organization_members
    WHERE organization_id = org_id
    AND user_id = auth.uid()
  );
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- RLS Policies for organizations
CREATE POLICY "Users can view their organizations"
  ON organizations FOR SELECT
  USING (is_organization_member(id));

-- RLS Policies for projects
CREATE POLICY "Organization members can view projects"
  ON projects FOR SELECT
  USING (is_organization_member(organization_id));

CREATE POLICY "Admins and owners can insert projects"
  ON projects FOR INSERT
  WITH CHECK (
    get_user_role(organization_id) IN ('admin', 'owner')
    AND auth.uid() = created_by
  );

CREATE POLICY "Admins and owners can update projects"
  ON projects FOR UPDATE
  USING (get_user_role(organization_id) IN ('admin', 'owner'));

CREATE POLICY "Owners can delete projects"
  ON projects FOR DELETE
  USING (get_user_role(organization_id) = 'owner');

-- Indexes for performance
CREATE INDEX idx_org_members_user ON organization_members(user_id);
CREATE INDEX idx_org_members_org ON organization_members(organization_id);
CREATE INDEX idx_projects_org ON projects(organization_id);

TypeScript Client Usage:

// types/database.ts
export interface Organization {
  id: string
  name: string
  created_at: string
}

export interface OrganizationMember {
  id: string
  organization_id: string
  user_id: string
  role: 'owner' | 'admin' | 'member'
  created_at: string
}

export interface Project {
  id: string
  organization_id: string
  name: string
  created_by: string
  created_at: string
}

// lib/supabase/organizations.ts
import { createClient } from '@supabase/supabase-js'
import type { Database } from '@/types/supabase'

export class OrganizationService {
  constructor(private supabase: ReturnType<typeof createClient<Database>>) {}

  async getOrganizations() {
    const { data, error } = await this.supabase
      .from('organizations')
      .select(`
        *,
        organization_members!inner(role)
      `)

    if (error) throw error
    return data
  }

  async getProjects(organizationId: string) {
    const { data, error} = await this.supabase
      .from('projects')
      .select('*')
      .eq('organization_id', organizationId)
      .order('created_at', { ascending: false })

    if (error) throw error
    return data
  }

  async createProject(organizationId: string, name: string) {
    const { data: { user } } = await this.supabase.auth.getUser()
    if (!user) throw new Error('Not authenticated')

    const { data, error } = await this.supabase
      .from('projects')
      .insert({
        organization_id: organizationId,
        name,
        created_by: user.id
      })
      .select()
      .single()

    if (error) throw error
    return data
  }
}

Pattern 2: Advanced Auth with Custom Claims

Search Strategy:

grep -r "custom.*claims\|jwt.*metadata\|user.*metadata" /Users/zach/Documents/cc-skills/docs/supabase/guides/auth/ -l
grep -r "auth.*hooks\|hook.*send" /Users/zach/Documents/cc-skills/docs/supabase/guides/auth/ --include="*.txt" -A 10

Implementation:

// lib/auth/custom-claims.ts
import { createClient } from '@supabase/supabase-js'

export interface UserClaims {
  role: 'admin' | 'user' | 'moderator'
  organization_id?: string
  permissions: string[]
}

export async function setUserClaims(
  userId: string,
  claims: UserClaims
): Promise<void> {
  const supabase = createClient(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY! // Service role required
  )

  const { error } = await supabase.auth.admin.updateUserById(
    userId,
    {
      app_metadata: { claims }
    }
  )

  if (error) throw error
}

export async function getUserClaims(userId: string): Promise<UserClaims | null> {
  const supabase = createClient(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!
  )

  const { data, error } = await supabase.auth.admin.getUserById(userId)

  if (error) throw error
  return data.user.app_metadata.claims as UserClaims || null
}

// Middleware for Next.js App Router
// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  const response = NextResponse.next()

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value
        },
        set(name: string, value: string, options: any) {
          response.cookies.set({ name, value, ...options })
        },
        remove(name: string, options: any) {
          response.cookies.set({ name, value: '', ...options })
        },
      },
    }
  )

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

  // Check if accessing admin route
  if (request.nextUrl.pathname.startsWith('/admin')) {
    if (!session) {
      return NextResponse.redirect(new URL('/login', request.url))
    }

    const claims = session.user.app_metadata.claims as UserClaims

    if (claims?.role !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', request.url))
    }
  }

  return response
}

export const config = {
  matcher: ['/admin/:path*', '/dashboard/:path*']
}

Pattern 3: Realtime with Presence and Broadcast

Search Strategy:

grep -r "presence\|broadcast\|realtime.*channel" /Users/zach/Documents/cc-skills/docs/supabase/guides/realtime/ -l

Implementation:

// hooks/usePresence.ts
import { useEffect, useState } from 'react'
import { createClient } from '@supabase/supabase-js'
import type { RealtimeChannel } from '@supabase/supabase-js'

interface PresenceState {
  [key: string]: {
    user_id: string
    username: string
    online_at: string
  }[]
}

export function usePresence(roomId: string) {
  const [presenceState, setPresenceState] = useState<PresenceState>({})
  const [channel, setChannel] = useState<RealtimeChannel | null>(null)

  useEffect(() => {
    const supabase = createClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
    )

    const presenceChannel = supabase.channel(`room:${roomId}`, {
      config: {
        presence: {
          key: 'user_id',
        },
      },
    })

    presenceChannel
      .on('presence', { event: 'sync' }, () => {
        const state = presenceChannel.presenceState()
        setPresenceState(state)
      })
      .on('presence', { event: 'join' }, ({ key, newPresences }) => {
        console.log('User joined:', key, newPresences)
      })
      .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
        console.log('User left:', key, leftPresences)
      })
      .subscribe(async (status) => {
        if (status === 'SUBSCRIBED') {
          const { data: { user } } = await supabase.auth.getUser()

          if (user) {
            await presenceChannel.track({
              user_id: user.id,
              username: user.email,
              online_at: new Date().toISOString(),
            })
          }
        }
      })

    setChannel(presenceChannel)

    return () => {
      presenceChannel.unsubscribe()
    }
  }, [roomId])

  const sendBroadcast = async (event: string, payload: any) => {
    if (channel) {
      await channel.send({
        type: 'broadcast',
        event,
        payload,
      })
    }
  }

  return {
    presenceState,
    onlineUsers: Object.values(presenceState).flat(),
    sendBroadcast,
  }
}

// Component usage
export function CollaborativeEditor({ documentId }: { documentId: string }) {
  const { onlineUsers, sendBroadcast } = usePresence(documentId)

  const handleCursorMove = (position: { x: number; y: number }) => {
    sendBroadcast('cursor_move', position)
  }

  return (
    <div>
      <div className="online-users">
        {onlineUsers.map(user => (
          <div key={user.user_id}>
            {user.username} (online)
          </div>
        ))}
      </div>
      {/* Editor component */}
    </div>
  )
}

Pattern 4: Vector Search with OpenAI Embeddings

Search Strategy:

grep -r "vector\|embedding\|pgvector\|similarity" /Users/zach/Documents/cc-skills/docs/supabase/guides/ai/ -l
grep -r "semantic.*search\|vector.*search" /Users/zach/Documents/cc-skills/docs/supabase/guides/ai/ --include="*.txt" -A 10

Implementation:

-- Enable pgvector extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Documents table with embeddings
CREATE TABLE documents (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  content TEXT NOT NULL,
  embedding vector(1536), -- OpenAI ada-002 dimension
  metadata JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Create index for vector similarity search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
  WITH (lists = 100);

-- Function for similarity search
CREATE OR REPLACE FUNCTION match_documents(
  query_embedding vector(1536),
  match_threshold float,
  match_count int
)
RETURNS TABLE (
  id UUID,
  content TEXT,
  metadata JSONB,
  similarity float
) LANGUAGE sql STABLE AS $$
  SELECT
    id,
    content,
    metadata,
    1 - (embedding <=> query_embedding) AS similarity
  FROM documents
  WHERE 1 - (embedding <=> query_embedding) > match_threshold
  ORDER BY embedding <=> query_embedding
  LIMIT match_count;
$$;

TypeScript Implementation:

// lib/ai/embeddings.ts
import { createClient } from '@supabase/supabase-js'
import OpenAI from 'openai'

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

export async function generateEmbedding(text: string): Promise<number[]> {
  const response = await openai.embeddings.create({
    model: 'text-embedding-ada-002',
    input: text,
  })

  return response.data[0].embedding
}

export async function addDocument(
  content: string,
  metadata: Record<string, any> = {}
): Promise<void> {
  const embedding = await generateEmbedding(content)

  const { error } = await supabase
    .from('documents')
    .insert({
      content,
      embedding,
      metadata,
    })

  if (error) throw error
}

export async function searchDocuments(
  query: string,
  matchThreshold: number = 0.78,
  matchCount: number = 10
) {
  const queryEmbedding = await generateEmbedding(query)

  const { data, error } = await supabase.rpc('match_documents', {
    query_embedding: queryEmbedding,
    match_threshold: matchThreshold,
    match_count: matchCount,
  })

  if (error) throw error
  return data
}

// RAG (Retrieval Augmented Generation) implementation
export async function ragQuery(userQuestion: string): Promise<string> {
  // 1. Get relevant documents
  const relevantDocs = await searchDocuments(userQuestion, 0.78, 5)

  // 2. Build context from documents
  const context = relevantDocs
    .map(doc => doc.content)
    .join('\n\n')

  // 3. Generate response with OpenAI
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: 'You are a helpful assistant. Answer questions based on the provided context.',
      },
      {
        role: 'user',
        content: `Context:\n${context}\n\nQuestion: ${userQuestion}`,
      },
    ],
  })

  return response.choices[0].message.content || ''
}

Pattern 5: Edge Functions with Background Jobs

Search Strategy:

grep -r "edge.*function\|deno\|background.*job" /Users/zach/Documents/cc-skills/docs/supabase/guides/functions/ -l

Implementation:

// supabase/functions/process-payment/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import Stripe from 'https://esm.sh/stripe@14.0.0'

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY') || '', {
  apiVersion: '2023-10-16',
})

const supabase = createClient(
  Deno.env.get('SUPABASE_URL') ?? '',
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
)

interface PaymentRequest {
  amount: number
  currency: string
  userId: string
  organizationId: string
}

serve(async (req) => {
  try {
    // 1. Authenticate request
    const authHeader = req.headers.get('Authorization')
    if (!authHeader) {
      return new Response(
        JSON.stringify({ error: 'Missing authorization' }),
        { status: 401 }
      )
    }

    const { data: { user }, error: authError } = await supabase.auth.getUser(
      authHeader.replace('Bearer ', '')
    )

    if (authError || !user) {
      return new Response(
        JSON.stringify({ error: 'Invalid authorization' }),
        { status: 401 }
      )
    }

    // 2. Parse request body
    const body: PaymentRequest = await req.json()

    // 3. Create Stripe payment intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount: body.amount,
      currency: body.currency,
      metadata: {
        user_id: body.userId,
        organization_id: body.organizationId,
      },
    })

    // 4. Store payment record in database
    const { error: dbError } = await supabase
      .from('payments')
      .insert({
        user_id: body.userId,
        organization_id: body.organizationId,
        stripe_payment_intent_id: paymentIntent.id,
        amount: body.amount,
        currency: body.currency,
        status: 'pending',
      })

    if (dbError) throw dbError

    // 5. Return client secret
    return new Response(
      JSON.stringify({
        clientSecret: paymentIntent.client_secret,
        paymentIntentId: paymentIntent.id,
      }),
      {
        headers: { 'Content-Type': 'application/json' },
        status: 200,
      }
    )

  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      {
        headers: { 'Content-Type': 'application/json' },
        status: 500,
      }
    )
  }
})

Framework-Specific Implementations

Next.js App Router (Complete Setup)

// lib/supabase/server.ts - Server Components
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'
import type { Database } from '@/types/supabase'

export function createClient() {
  const cookieStore = cookies()

  return createServerClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options })
          } catch (error) {
            // Server Component can't set cookies
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: '', ...options })
          } catch (error) {
            // Server Component can't delete cookies
          }
        },
      },
    }
  )
}

// lib/supabase/client.ts - Client Components
import { createBrowserClient } from '@supabase/ssr'
import type { Database } from '@/types/supabase'

export function createClient() {
  return createBrowserClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

// app/auth/callback/route.ts - Auth callback handler
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get('code')
  const next = searchParams.get('next') ?? '/dashboard'

  if (code) {
    const supabase = createClient()
    const { error } = await supabase.auth.exchangeCodeForSession(code)

    if (!error) {
      return NextResponse.redirect(`${origin}${next}`)
    }
  }

  return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}

// app/dashboard/page.tsx - Server Component with data fetching
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const supabase = createClient()

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

  if (!user) {
    redirect('/login')
  }

  const { data: projects } = await supabase
    .from('projects')
    .select('*')
    .order('created_at', { ascending: false })

  return (
    <div>
      <h1>Welcome {user.email}</h1>
      <ProjectsList projects={projects || []} />
    </div>
  )
}

// app/actions/projects.ts - Server Actions
'use server'

import { createClient } from '@/lib/supabase/server'
import { revalidatePath } from 'next/cache'

export async function createProject(formData: FormData) {
  const supabase = createClient()

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

  if (!user) {
    return { error: 'Not authenticated' }
  }

  const name = formData.get('name') as string
  const organizationId = formData.get('organizationId') as string

  const { data, error } = await supabase
    .from('projects')
    .insert({
      name,
      organization_id: organizationId,
      created_by: user.id,
    })
    .select()
    .single()

  if (error) {
    return { error: error.message }
  }

  revalidatePath('/dashboard')
  return { data }
}

export async function deleteProject(projectId: string) {
  const supabase = createClient()

  const { error } = await supabase
    .from('projects')
    .delete()
    .eq('id', projectId)

  if (error) {
    return { error: error.message }
  }

  revalidatePath('/dashboard')
  return { success: true }
}

Performance Optimization Strategies

Strategy 1: Connection Pooling for Serverless

Search:

grep -r "connection.*pool\|supavisor\|serverless" /Users/zach/Documents/cc-skills/docs/supabase/guides/ -l

Implementation:

// lib/supabase/connection-pool.ts
import { createClient } from '@supabase/supabase-js'

// Use connection pooler for serverless environments
const POOLER_URL = process.env.SUPABASE_URL?.replace(
  '.supabase.co',
  '.pooler.supabase.com'
)

export function createPooledClient() {
  return createClient(
    POOLER_URL || process.env.SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    {
      db: {
        schema: 'public',
      },
      auth: {
        persistSession: false,
      },
    }
  )
}

// Use in API routes
export async function GET(request: Request) {
  const supabase = createPooledClient()

  const { data, error } = await supabase
    .from('large_table')
    .select('*')
    .limit(1000)

  // Connection automatically returned to pool

  return Response.json({ data, error })
}

Strategy 2: Query Optimization

// lib/supabase/optimized-queries.ts

// ❌ BAD: Fetches all columns and data
const { data } = await supabase
  .from('users')
  .select('*')

// ✅ GOOD: Select only needed columns
const { data } = await supabase
  .from('users')
  .select('id, email, username')

// ✅ GOOD: Use pagination
const { data, error, count } = await supabase
  .from('users')
  .select('*', { count: 'exact' })
  .range(0, 49) // First 50 items
  .order('created_at', { ascending: false })

// ✅ GOOD: Use joins efficiently
const { data } = await supabase
  .from('posts')
  .select(`
    id,
    title,
    author:users!inner(id, username),
    comments(count)
  `)
  .eq('published', true)
  .limit(10)

// ✅ GOOD: Use indexes (ensure indexes exist)
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('user_id', userId) // Indexed column
  .eq('status', 'published') // Indexed column

Strategy 3: Caching Layer

// lib/cache/supabase-cache.ts
import { createClient } from '@supabase/supabase-js'
import { unstable_cache } from 'next/cache'

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_ANON_KEY!
)

// Cache queries with Next.js cache
export const getCachedProjects = unstable_cache(
  async (organizationId: string) => {
    const { data, error } = await supabase
      .from('projects')
      .select('*')
      .eq('organization_id', organizationId)
      .order('created_at', { ascending: false })

    if (error) throw error
    return data
  },
  ['projects'], // Cache key
  {
    revalidate: 60, // Revalidate every 60 seconds
    tags: ['projects'], // For manual revalidation
  }
)

// Use with revalidation
import { revalidateTag } from 'next/cache'

export async function createProject(name: string, orgId: string) {
  const { data, error } = await supabase
    .from('projects')
    .insert({ name, organization_id: orgId })
    .select()
    .single()

  if (!error) {
    revalidateTag('projects') // Invalidate cache
  }

  return { data, error }
}

Security Audit Checklist

Critical Security Checks

// scripts/security-audit.ts

interface SecurityAudit {
  checks: SecurityCheck[]
  passed: boolean
  failures: string[]
}

interface SecurityCheck {
  name: string
  passed: boolean
  message?: string
}

export async function auditSupabaseSecurity(): Promise<SecurityAudit> {
  const checks: SecurityCheck[] = []

  // 1. Check for exposed service role key
  const serviceKeyCheck = !process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY
  checks.push({
    name: 'Service Role Key Security',
    passed: serviceKeyCheck,
    message: serviceKeyCheck
      ? 'Service role key not exposed in public env vars'
      : '❌ CRITICAL: Service role key exposed in public env vars!',
  })

  // 2. Check RLS is enabled on all tables
  // (Run in Supabase SQL editor or via service role client)
  const supabase = createClient(
    process.env.SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!
  )

  const { data: tables } = await supabase.rpc('check_rls_enabled')
  const rlsCheck = tables?.every(t => t.rls_enabled)

  checks.push({
    name: 'RLS Enabled on All Tables',
    passed: rlsCheck || false,
    message: rlsCheck
      ? 'All tables have RLS enabled'
      : '⚠️ Some tables missing RLS policies',
  })

  // 3. Check for SQL injection vulnerabilities
  // Scan codebase for string concatenation in queries
  const sqlInjectionCheck = true // Implement static analysis
  checks.push({
    name: 'No SQL Injection Risks',
    passed: sqlInjectionCheck,
  })

  // 4. Check HTTPS only
  const httpsCheck = process.env.SUPABASE_URL?.startsWith('https://')
  checks.push({
    name: 'HTTPS Only',
    passed: httpsCheck || false,
    message: httpsCheck ? 'Using HTTPS' : '❌ Not using HTTPS!',
  })

  const passed = checks.every(check => check.passed)
  const failures = checks
    .filter(check => !check.passed)
    .map(check => check.message || check.name)

  return { checks, passed, failures }
}

Testing Utilities

RLS Policy Testing

// tests/rls-policies.test.ts
import { createClient } from '@supabase/supabase-js'
import { describe, it, expect, beforeAll } from 'vitest'

describe('RLS Policies', () => {
  let supabase: ReturnType<typeof createClient>
  let testUserId: string

  beforeAll(async () => {
    // Create test user
    supabase = createClient(
      process.env.SUPABASE_URL!,
      process.env.SUPABASE_SERVICE_ROLE_KEY!
    )

    const { data: { user } } = await supabase.auth.admin.createUser({
      email: 'test@example.com',
      password: 'test-password-123',
      email_confirm: true,
    })

    testUserId = user!.id
  })

  it('should allow users to read their own data', async () => {
    // Sign in as test user
    const { data: { session } } = await supabase.auth.signInWithPassword({
      email: 'test@example.com',
      password: 'test-password-123',
    })

    const userClient = createClient(
      process.env.SUPABASE_URL!,
      process.env.SUPABASE_ANON_KEY!,
      {
        global: {
          headers: {
            Authorization: `Bearer ${session!.access_token}`,
          },
        },
      }
    )

    const { data, error } = await userClient
      .from('users')
      .select('*')
      .eq('id', testUserId)

    expect(error).toBeNull()
    expect(data).toHaveLength(1)
  })

  it('should prevent users from reading other users data', async () => {
    const { data: { session } } = await supabase.auth.signInWithPassword({
      email: 'test@example.com',
      password: 'test-password-123',
    })

    const userClient = createClient(
      process.env.SUPABASE_URL!,
      process.env.SUPABASE_ANON_KEY!,
      {
        global: {
          headers: {
            Authorization: `Bearer ${session!.access_token}`,
          },
        },
      }
    )

    const { data, error } = await userClient
      .from('users')
      .select('*')
      .neq('id', testUserId)

    expect(data).toHaveLength(0) // RLS should filter out other users
  })
})

Error Handling Framework

Comprehensive Error Handler

// lib/errors/supabase-errors.ts
import { PostgrestError } from '@supabase/supabase-js'

export class SupabaseError extends Error {
  constructor(
    public code: string,
    public details: string,
    public hint?: string
  ) {
    super(details)
    this.name = 'SupabaseError'
  }
}

export function handleSupabaseError(error: PostgrestError | null): never {
  if (!error) {
    throw new Error('Unknown error occurred')
  }

  // RLS Policy Violation
  if (error.code === '42501' || error.message.includes('policy')) {
    throw new SupabaseError(
      'RLS_POLICY_VIOLATION',
      'You do not have permission to perform this action',
      'Check row-level security policies'
    )
  }

  // Unique Constraint Violation
  if (error.code === '23505') {
    const field = error.message.match(/Key \((.*?)\)/)?.[1]
    throw new SupabaseError(
      'DUPLICATE_ENTRY',
      `A record with this ${field} already exists`,
      'Use a different value or update the existing record'
    )
  }

  // Foreign Key Violation
  if (error.code === '23503') {
    throw new SupabaseError(
      'INVALID_REFERENCE',
      'The referenced record does not exist',
      'Ensure the related record exists before creating this one'
    )
  }

  // Connection Error
  if (error.message.includes('Failed to fetch')) {
    throw new SupabaseError(
      'CONNECTION_ERROR',
      'Unable to connect to the database',
      'Check your network connection and Supabase status'
    )
  }

  // Generic Error
  throw new SupabaseError(
    error.code || 'UNKNOWN_ERROR',
    error.message,
    error.hint
  )
}

// Usage
try {
  const { data, error } = await supabase
    .from('users')
    .insert({ email: 'test@example.com' })

  if (error) handleSupabaseError(error)

  return data
} catch (err) {
  if (err instanceof SupabaseError) {
    console.error(`[${err.code}] ${err.details}`)
    if (err.hint) console.error(`Hint: ${err.hint}`)

    // Return user-friendly error
    return {
      error: true,
      message: err.details,
      code: err.code,
    }
  }
  throw err
}

Monitoring & Observability

// lib/monitoring/supabase-logger.ts
import { createClient } from '@supabase/supabase-js'

interface QueryLog {
  query: string
  duration: number
  error?: string
  timestamp: string
}

export class SupabaseMonitor {
  private logs: QueryLog[] = []

  constructor(private supabase: ReturnType<typeof createClient>) {
    this.wrapClient()
  }

  private wrapClient() {
    const originalFrom = this.supabase.from.bind(this.supabase)

    this.supabase.from = (table: string) => {
      const startTime = Date.now()
      const builder = originalFrom(table)

      const wrapMethod = (method: string) => {
        const original = (builder as any)[method].bind(builder)
        ;(builder as any)[method] = async (...args: any[]) => {
          const result = await original(...args)
          const duration = Date.now() - startTime

          this.logs.push({
            query: `${method} on ${table}`,
            duration,
            error: result.error?.message,
            timestamp: new Date().toISOString(),
          })

          // Log slow queries
          if (duration > 1000) {
            console.warn(`Slow query detected: ${method} on ${table} took ${duration}ms`)
          }

          return result
        }
      }

      ;['select', 'insert', 'update', 'delete', 'upsert'].forEach(wrapMethod)

      return builder
    }
  }

  getLogs() {
    return this.logs
  }

  getSlowQueries(threshold = 1000) {
    return this.logs.filter(log => log.duration > threshold)
  }

  getErrorRate() {
    const totalQueries = this.logs.length
    const errorQueries = this.logs.filter(log => log.error).length
    return totalQueries > 0 ? errorQueries / totalQueries : 0
  }
}

Migration Utilities

Migration from Firebase

// scripts/migrate-from-firebase.ts
import admin from 'firebase-admin'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

admin.initializeApp({
  credential: admin.credential.cert('./firebase-credentials.json'),
})

export async function migrateUsers() {
  const auth = admin.auth()
  let nextPageToken: string | undefined

  do {
    const listUsersResult = await auth.listUsers(1000, nextPageToken)

    for (const userRecord of listUsersResult.users) {
      try {
        // Create user in Supabase
        const { data, error } = await supabase.auth.admin.createUser({
          email: userRecord.email!,
          email_confirm: true,
          user_metadata: {
            name: userRecord.displayName,
            avatar_url: userRecord.photoURL,
            migrated_from_firebase: true,
          },
        })

        if (error) {
          console.error(`Failed to migrate user ${userRecord.email}:`, error)
          continue
        }

        console.log(`Migrated user: ${userRecord.email}`)
      } catch (err) {
        console.error(`Error migrating user ${userRecord.email}:`, err)
      }
    }

    nextPageToken = listUsersResult.pageToken
  } while (nextPageToken)
}

export async function migrateFirestoreCollection(
  collectionName: string,
  tableName: string
) {
  const firestore = admin.firestore()
  const snapshot = await firestore.collection(collectionName).get()

  for (const doc of snapshot.docs) {
    const data = doc.data()

    // Transform Firestore document to Supabase row
    const row = {
      id: doc.id,
      ...data,
      // Convert Firestore timestamps
      created_at: data.createdAt?._seconds
        ? new Date(data.createdAt._seconds * 1000).toISOString()
        : null,
    }

    const { error } = await supabase
      .from(tableName)
      .insert(row)

    if (error) {
      console.error(`Failed to migrate document ${doc.id}:`, error)
    } else {
      console.log(`Migrated document: ${doc.id}`)
    }
  }
}

Output Format

When providing Supabase guidance, follow this comprehensive format:

1. Deep Analysis

  • Understand user's context (framework, scale, requirements)
  • Identify potential challenges and edge cases
  • Consider security implications

2. Documentation Research

  • Cite specific documentation files consulted
  • Reference API documentation for exact signatures
  • Cross-reference related features

3. Production-Grade Implementation

  • Complete TypeScript code with all types
  • Comprehensive error handling
  • Loading states and edge cases
  • Performance optimizations built-in
  • Security best practices applied
  • Monitoring/logging hooks

4. Testing Strategy

  • Unit test examples
  • Integration test scenarios
  • RLS policy testing
  • E2E test guidance

5. Deployment Guidance

  • Environment variable setup
  • Migration scripts
  • Rollback procedures
  • Monitoring setup

6. Performance Considerations

  • Query optimization tips
  • Caching strategies
  • Connection pooling guidance
  • Index recommendations

7. Security Review

  • RLS policy review
  • Input validation
  • API key security
  • CORS configuration

8. Next Steps & Scaling

  • Related features to implement
  • Scaling considerations
  • Advanced patterns to explore

Notes

  • Always search documentation first - Consult 2,190 pages before answering
  • PostgreSQL expertise required - Supabase is PostgreSQL, apply PG best practices
  • Deno for Edge Functions - Not Node.js, different module system
  • RLS is mandatory - Test thoroughly, security is critical
  • Type generation is essential - Always generate types from schema
  • Connection pooling - Required for serverless/Edge deployments
  • Service role = superuser - Never expose to clients
  • Anon key is safe - Can be used in client-side code
  • Local development - Requires Docker for Supabase CLI
  • Test RLS exhaustively - Use multiple user contexts

Documentation Search Shortcuts

# Quick searches by topic
alias sb-auth="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/guides/auth/ -l"
alias sb-db="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/guides/database/ -l"
alias sb-storage="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/guides/storage/ -l"
alias sb-realtime="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/guides/realtime/ -l"
alias sb-functions="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/guides/functions/ -l"
alias sb-ai="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/guides/ai/ -l"

# Find API reference
alias sb-api="grep -r '$1' /Users/zach/Documents/cc-skills/docs/supabase/reference/ -l | head -20"

Quick Reference Commands

# Local Development
supabase init                                    # Initialize project
supabase start                                   # Start local Supabase (requires Docker)
supabase status                                  # Check local setup status
supabase stop                                    # Stop local instance

# Database Operations
supabase migration new <name>                    # Create new migration
supabase db reset                                # Reset local database
supabase db push                                 # Push migrations to remote
supabase db pull                                 # Pull remote schema
supabase db diff                                 # Show schema differences

# Type Generation
supabase gen types typescript --local            # Generate types from local
supabase gen types typescript --project-id <id>  # Generate from remote

# Edge Functions
supabase functions new <name>                    # Create new function
supabase functions serve                         # Test functions locally
supabase functions deploy <name>                 # Deploy function
supabase functions logs <name>                   # View function logs

# Authentication
supabase auth users list                         # List users
supabase auth users get <user-id>               # Get user details

# Secrets Management
supabase secrets set SECRET_NAME=value          # Set secret
supabase secrets list                           # List secrets