← All posts
·11 min read

Claude Code and Supabase: The Complete Guide

Claude CodeSupabaseWorkflowIntegration
Claude Code and Supabase: The Complete Guide

Claude Code and Supabase

Supabase is the default Postgres stack for solo developers and small teams who need auth, storage, and a database without managing infrastructure. Claude Code is the terminal-based coding agent that can query your database, write your Row Level Security policies, and scaffold your auth flows.

The combination is genuinely powerful. Claude Code reads your Supabase schema, understands your RLS rules, writes Edge Functions, and generates type-safe TypeScript clients. The question is how to wire them up properly so Claude Code has the context to do the work well.

This guide covers: MCP setup for direct database access, auth flow generation, Row Level Security policies, migrations, Edge Functions, and the CLAUDE.md patterns that make the whole setup repeatable.

Setting up the Supabase MCP server

The most direct path is the official Supabase MCP server. It gives Claude Code live access to your project during development: tables, schemas, functions, auth configuration, and direct SQL execution.

First, get your Supabase project reference and a personal access token from your Supabase dashboard. Then add the MCP server to your .mcp.json:

{
  "mcpServers": {
    "supabase": {
      "command": "npx",
      "args": ["-y", "@supabase/mcp-server-supabase@latest", "--project-ref", "your-project-ref"],
      "env": {
        "SUPABASE_ACCESS_TOKEN": "your-personal-access-token"
      }
    }
  }
}

Once configured, Claude Code can query your database directly. Ask it to inspect your schema, run a migration, or pull sample data and it executes against your live project. For a full walkthrough of MCP setup patterns, see our Claude Code MCP servers guide.

Keep .mcp.json in your .gitignore. Personal access tokens give broad account access, not just project access, so treat them like a root password.

What the MCP server exposes

With the Supabase MCP server connected, Claude Code gains access to:

  • Direct SQL execution against your project database
  • Schema inspection (tables, columns, indexes, constraints, views)
  • Auth configuration (providers, JWT settings, email templates)
  • Storage buckets and policies
  • Project configuration and API keys

Claude can read the structure of your entire project without you pasting schemas into the chat. This is the difference between Claude Code guessing at your data model and actually knowing it.

Telling Claude Code about your Supabase setup

The MCP server gives Claude live access. Your CLAUDE.md file gives Claude the context to use that access well. Add a section describing your Supabase conventions:

## Database (Supabase / Postgres)

Project ref: xyzabcdefgh
Region: us-east-1

### Schema conventions
- All tables use UUID primary keys with `gen_random_uuid()`
- Timestamps: `created_at` and `updated_at` with `now()` defaults
- Soft deletes: `deleted_at TIMESTAMPTZ NULL` (null = active)
- All user-facing tables have RLS enabled

### Auth
- Auth provider: Supabase Auth (email + Google OAuth)
- JWT claims include: user_id, email, role, subscription_tier
- Row ownership: `auth.uid()` matches `user_id` column

### RLS pattern
- SELECT: users read their own rows only
- INSERT: auth.uid() must equal user_id on new rows
- UPDATE/DELETE: auth.uid() must equal user_id
- Service role bypasses RLS. Use it only in Edge Functions with sensitive operations

### TypeScript client
- Use `@supabase/supabase-js` v2
- Client is initialized in `src/lib/supabase.ts`
- Server-side client uses service role key, browser client uses anon key

With this in place, every prompt to Claude Code for Supabase work starts from the right baseline. You do not need to re-explain your schema conventions each session.

Building auth flows with Claude Code

Supabase Auth handles the heavy lifting for authentication, but you still need to wire it into your application: session management, protected routes, token refresh, and provider callbacks. Claude Code builds all of this from a clear prompt.

For a Next.js application with email and Google OAuth:

Build the full auth flow for this Next.js app using Supabase Auth:
- Email/password sign up and sign in with email verification
- Google OAuth with a callback handler
- Session management with middleware protecting /dashboard/*
- useUser hook for client components
- Server-side auth for API routes
- Redirect to /login on expired sessions

Claude Code generates the Supabase client helpers, the OAuth callback route, the middleware, and the client-side hooks. It reads your project structure first to place files correctly and match your existing import patterns.

A typical auth helper output:

// src/lib/supabase/server.ts
import { createServerClient } 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: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          )
        },
      },
    }
  )
}

Claude Code writes this correctly on the first pass because it knows you are using Next.js App Router (from your project structure), @supabase/ssr (from your package.json), and your Database type (from your generated types file). Vague prompts produce generic code. Specific prompts with project context produce production-ready code.

Row Level Security policies

RLS is where most developers lose time with Supabase. Policies need to be precise, and debugging a misconfigured policy is frustrating because the error messages are opaque. Claude Code is genuinely good at RLS because it can hold your entire data model in context while reasoning about access patterns.

Describe what you need, not the SQL:

Write RLS policies for the `documents` table. Rules:
- Any authenticated user can read documents where is_public = true
- Document owners can read, update, and delete their own documents
- Team members can read documents in their team (join through team_memberships)
- Only the document owner can change is_public
- Admins (role = 'admin' in JWT claims) can read all documents

Claude Code generates:

-- Enable RLS
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

-- Public documents readable by all authenticated users
CREATE POLICY "authenticated_read_public_documents"
ON documents FOR SELECT
TO authenticated
USING (is_public = true);

-- Owners have full access to their documents
CREATE POLICY "owners_full_access"
ON documents FOR ALL
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

-- Team members can read team documents
CREATE POLICY "team_members_read_team_documents"
ON documents FOR SELECT
TO authenticated
USING (
  EXISTS (
    SELECT 1 FROM team_memberships tm
    WHERE tm.team_id = documents.team_id
    AND tm.user_id = auth.uid()
    AND tm.deleted_at IS NULL
  )
);

-- Only owners can toggle is_public
CREATE POLICY "owners_update_visibility"
ON documents FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (
  auth.uid() = user_id
  AND (
    is_public = (SELECT is_public FROM documents WHERE id = documents.id)
    OR auth.uid() = user_id
  )
);

-- Admins can read all documents
CREATE POLICY "admins_read_all"
ON documents FOR SELECT
TO authenticated
USING (
  (auth.jwt() ->> 'role') = 'admin'
);

After generating, ask Claude to verify the policies against specific scenarios: "Can a team member delete a document they don't own?" Claude traces through the policy logic and confirms or identifies gaps.

For your CLAUDE.md, add a note about RLS verification:

### RLS verification pattern
After writing new policies, always test with:
1. A query as the document owner (should succeed)
2. A query as a non-member (should fail with 0 rows)
3. A query as a team member (should return team docs only)
Use `set role authenticated; set request.jwt.claims = '{"sub": "user-id"}';` in SQL editor for manual testing.

Database migrations with Supabase

Supabase uses a migration system in supabase/migrations/. Claude Code generates correctly named migration files and handles the numbering:

Add a `subscription_tier` column to the users table.
Enum values: free, pro, enterprise. Default to free.
Update the existing RLS policies to include subscription_tier in SELECT output.
Also add an index on subscription_tier for filtering.

Claude Code:

  1. Reads your existing migration files to determine the next sequence number
  2. Generates a new migration file: supabase/migrations/20260507120000_add_subscription_tier.sql
  3. Writes the forward migration (ALTER TABLE, CREATE INDEX)
  4. Optionally writes a rollback if your team uses them
  5. Updates any TypeScript types or Drizzle/Prisma schema if you have ORM types
-- supabase/migrations/20260507120000_add_subscription_tier.sql

ALTER TABLE users ADD COLUMN subscription_tier TEXT NOT NULL DEFAULT 'free'
  CHECK (subscription_tier IN ('free', 'pro', 'enterprise'));

CREATE INDEX idx_users_subscription_tier ON users(subscription_tier);

COMMENT ON COLUMN users.subscription_tier IS 'User subscription level: free, pro, or enterprise';

Apply via the Supabase CLI: supabase db push for remote, supabase db reset for local. Claude Code knows both commands and will tell you which is appropriate based on your workflow.

Generating TypeScript types

Supabase generates TypeScript types from your database schema via the CLI. Claude Code automates the full type generation and integration workflow:

Generate TypeScript types from the current Supabase schema.
Update the types file at src/types/supabase.ts.
Then check all existing queries for type errors caused by the new columns.

Claude Code runs npx supabase gen types typescript --project-id your-ref > src/types/supabase.ts, reads the updated types, and scans your query files for type mismatches. When it finds a query returning a column that is now typed differently, it flags or auto-fixes it.

This is the kind of cascading change that takes hours to track down manually. Claude Code does it in a single pass because it reads the whole codebase, not just the file you are editing.

Edge Functions

Supabase Edge Functions run on Deno at the edge. They are the right place for webhook handlers, server-side operations that need the service role key, and scheduled jobs. Claude Code understands Deno's import model and the Supabase Edge runtime.

A typical prompt for a webhook handler:

Write a Supabase Edge Function at supabase/functions/on-user-created/index.ts.
Triggered when a new user row is inserted via a database webhook.
It should:
- Verify the webhook signature using the WEBHOOK_SECRET env var
- Create a Stripe customer for the user
- Store the Stripe customer ID back in the users table
- Send a welcome email via Resend
- Return 200 on success, 500 on failure with error details

Claude Code generates the complete function with signature verification, Stripe client initialization, database write-back, and structured error handling. Because it knows your Supabase schema (user table structure, column names), the database queries it generates match your actual table.

// supabase/functions/on-user-created/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'

serve(async (req) => {
  const signature = req.headers.get('x-webhook-signature')
  const body = await req.text()

  // Verify signature
  const expectedSig = await crypto.subtle.sign(
    'HMAC',
    await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(Deno.env.get('WEBHOOK_SECRET')!),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    ),
    new TextEncoder().encode(body)
  )

  const expectedHex = Array.from(new Uint8Array(expectedSig))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')

  if (signature !== expectedHex) {
    return new Response('Unauthorized', { status: 401 })
  }

  const payload = JSON.parse(body)
  const user = payload.record

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

  const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {
    apiVersion: '2026-04-22.dahlia',
  })

  const customer = await stripe.customers.create({
    email: user.email,
    metadata: { supabase_user_id: user.id },
  })

  await supabase
    .from('users')
    .update({ stripe_customer_id: customer.id })
    .eq('id', user.id)

  return new Response(JSON.stringify({ success: true }), {
    headers: { 'Content-Type': 'application/json' },
  })
})

For environment variable management in Edge Functions, Claude Code knows to use Deno.env.get() rather than process.env, and will remind you to set the secrets via supabase secrets set.

Using Claude Code hooks for database safety

Production databases need guardrails. Claude Code's hook system lets you intercept dangerous operations before they run.

A PreToolUse hook blocking destructive SQL:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'echo \"$CLAUDE_TOOL_INPUT\" | grep -iE \"DROP TABLE|TRUNCATE|DELETE FROM users\" && echo \"BLOCKED: destructive SQL detected\" && exit 1 || exit 0'"
          }
        ]
      }
    ]
  }
}

Add this to your Claude Code settings and Claude cannot accidentally execute table drops or bulk user deletes. It can still run them if you explicitly approve after the block surfaces the warning.

For your local development workflow with Supabase, also use supabase start with the local stack before writing queries against the remote database. Claude Code understands both contexts and will ask which you are targeting if it is ambiguous.

Full workflow: new feature with Supabase

Here is how a complete feature development session looks when Claude Code is properly configured with the Supabase MCP server and a solid CLAUDE.md:

  1. Describe the feature. "Add a comments system to documents. Users can post comments, edit their own, and delete their own. Admins can delete any comment."

  2. Claude Code inspects the schema. It reads the existing documents table, understands your UUID and timestamp conventions, notes your existing RLS patterns.

  3. Claude Code generates the migration. New comments table with correct foreign keys, indexes, and soft delete column.

  4. Claude Code writes RLS policies. Based on your stated rules, following your existing policy naming convention.

  5. Claude Code generates TypeScript types. Runs the type generator and updates the types file.

  6. Claude Code scaffolds the API. Server actions or API routes for creating, updating, and deleting comments, with the correct Supabase client (server-side, using your server helper).

  7. Claude Code writes tests. Unit tests for the API functions, mocking the Supabase client.

The whole thing takes a conversation, not a day. The MCP server and CLAUDE.md context are what make this possible: Claude Code is not guessing at your setup, it is reading it.

Setting up for the first time

If you are starting from scratch with this setup, follow the Claude Code setup guide first to get the basic environment running. Then:

  1. Install the Supabase CLI: npm install -g supabase
  2. Link your project: supabase link --project-ref your-ref
  3. Add the MCP server to .mcp.json (see above)
  4. Generate your initial TypeScript types: npx supabase gen types typescript --project-id your-ref > src/types/supabase.ts
  5. Add the Supabase section to your CLAUDE.md

With these four pieces in place, Claude Code has everything it needs for productive Supabase development.

FAQ

Can Claude Code access my production Supabase database?

Yes, via the MCP server configured with your production project ref. However, use a read-only role for exploration and reserve the service role for deliberate write operations. Set up a separate local Supabase stack with supabase start for development work.

Does Claude Code understand RLS?

Yes. Because Claude reads your schema and can execute queries through the MCP server, it can reason about RLS policies with the actual table structure in context. It generates policies, verifies their logic, and tests edge cases. This is more reliable than writing RLS from scratch because Claude holds all the table relationships at once.

How does Claude Code handle Supabase environment variables?

Claude Code looks for NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY for browser clients, and SUPABASE_SERVICE_ROLE_KEY for server-side operations. If you document these in your CLAUDE.md, Claude will never prompt you to add them and will always use the right key for the right context. See our environment variables guide for full credential management patterns.

Can I use Claude Code with Supabase local development?

Yes. Run supabase start to spin up a local stack. Claude Code uses the local URLs and keys (output by supabase status) when you specify the local environment. Add local and remote configs to your CLAUDE.md so Claude knows which to target for which tasks.


Get Claudify: pre-configured Supabase commands, RLS policy templates, and auth flow scaffolds. Installed in one command: npx create-claudify.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir