Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
bjornmelin avatar

Supabase Ts

  • 8 installs
  • 5 repo stars
  • Updated August 5, 2026
  • bjornmelin/dev-skills

Supabase TS is a Claude Code skill providing production Supabase integration patterns for Next.js, React, and TypeScript apps.

About

Supabase TS gives production integration patterns for using Supabase in Next.js, React, and TypeScript apps. It shows SSR-correct server, browser, and middleware clients, plus auth, Row Level Security, storage, realtime, and Edge Functions patterns. A developer uses it when wiring Supabase into a Next.js App Router project and wants to avoid common auth and RLS mistakes. It also covers pgvector semantic search, Vercel connection pooling, and Zod v4 response validation.

  • SSR-correct server, browser, and middleware Supabase clients for Next.js App Router with @supabase/ssr
  • Auth uses getUser() to validate the JWT and RLS wraps auth.uid() in a subquery
  • Covers storage signed URLs, realtime broadcast channels, pgvector search, and Zod v4 validation

Supabase Ts by the numbers

  • 8 all-time installs (skills.sh)
  • Ranked #3,619 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

supabase-ts capabilities & compatibility

Capabilities
supabase auth · row level security · storage uploads · realtime channels · vector search · edge functions
Works with
supabase · postgres · vercel
Use cases
api development · database · security audit
IDEs
vscode · cursor ide
From the docs

What supabase-ts says it does

// CORRECT: Validates JWT with auth server
SKILL.md
Production patterns for Supabase in Next.js/React/Vercel applications with TypeScript and Zod v4.
SKILL.md
npx skills add https://github.com/bjornmelin/dev-skills --skill supabase-ts

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs8
repo stars5
Last updatedAugust 5, 2026
Repositorybjornmelin/dev-skills

What it does

Wire Supabase auth, RLS, storage, realtime, and Edge Functions into Next.js/TypeScript apps with SSR-correct clients.

Who is it for?

Integrating Supabase auth, RLS, storage, realtime, and Edge Functions correctly into a Next.js App Router app.

Skip if: Non-Supabase backends or non-TypeScript stacks.

When should I use this skill?

Setting up Supabase clients, auth, RLS policies, storage uploads, realtime, Edge Functions, or vector search in Next.js.

What you get

A Next.js app with SSR-safe Supabase clients, validated auth, efficient RLS, and secure storage.

  • Supabase client setup
  • RLS policies
  • auth/middleware wiring

By the numbers

  • 9 reference guides bundled
  • 5-context client decision table

Files

SKILL.mdMarkdownGitHub ↗

Supabase TypeScript

Production patterns for Supabase in Next.js/React/Vercel applications with TypeScript and Zod v4.

Quick Reference

Server Client (Next.js App Router)

// src/lib/supabase/server.ts
import "server-only";
import { cookies } from "next/headers";
import { createServerClient } from "@supabase/ssr";
import type { Database } from "./database.types";

export async function createServerSupabase() {
  const cookieStore = await cookies();
  return createServerClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => cookieStore.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) => {
            cookieStore.set(name, value, options);
          });
        },
      },
    }
  );
}

Browser Client (React)

// src/lib/supabase/client.ts
import { createBrowserClient } from "@supabase/ssr";
import type { Database } from "./database.types";

let client: ReturnType<typeof createBrowserClient<Database>> | null = null;

export function getBrowserClient() {
  if (client) return client;
  if (typeof window === "undefined") return null;

  client = createBrowserClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
  return client;
}

Middleware Client

// middleware.ts
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const response = NextResponse.next({ request });
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll: () => request.cookies.getAll(),
        setAll: (cookiesToSet) => {
          cookiesToSet.forEach(({ name, value, options }) => {
            response.cookies.set(name, value, options);
          });
        },
      },
    }
  );

  const { data: { user } } = await supabase.auth.getUser();
  if (!user && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return response;
}

Decision Framework

ContextClientWhy
Server ComponentcreateServerSupabase()Async cookies, server-only
Route HandlercreateServerSupabase()SSR context with cookies
MiddlewareInline createServerClientEdge runtime, request/response cookies
Client ComponentgetBrowserClient()Singleton, SSR-safe (null check)
Server ActioncreateServerSupabase()Server context

Core Patterns

Auth: Always Use getUser()

// CORRECT: Validates JWT with auth server
const { data: { user } } = await supabase.auth.getUser();

// WRONG: Only reads from cookie, can be spoofed
const { data: { session } } = await supabase.auth.getSession();

RLS: Use Subquery Wrapper

-- CORRECT: Subquery prevents multiple auth.uid() calls
create policy "Users view own data"
on public.items for select
to authenticated
using ((select auth.uid()) = user_id);

-- WRONG: Direct call, inefficient
using (auth.uid() = user_id);

Realtime: Prefer Broadcast

// Broadcast: Low latency, no DB polling
const channel = supabase.channel("room:123", { config: { private: true } });
channel.send({ type: "broadcast", event: "cursor", payload: { x, y } });

// postgres_changes: Higher latency, DB trigger required
channel.on("postgres_changes", { event: "*", schema: "public", table: "messages" }, handler);

Storage: Signed URLs for Private Files

// Public bucket: Direct URL
const { data } = supabase.storage.from("public-bucket").getPublicUrl("file.jpg");

// Private bucket: Time-limited signed URL
const { data } = await supabase.storage
  .from("private-bucket")
  .createSignedUrl("file.jpg", 3600); // 1 hour

Zod v4 Integration

import { z } from "zod";

// Use top-level string helpers (Zod v4)
const UserSchema = z.strictObject({
  id: z.uuid(),
  email: z.email(),
  created_at: z.iso.datetime(),
  metadata: z.looseObject({
    avatar_url: z.url().optional(),
  }),
});

// Unified error option (Zod v4)
const InsertSchema = z.strictObject({
  title: z.string().min(1, { error: "Title required" }),
  user_id: z.uuid({ error: "Invalid user ID" }),
});

// Parse Supabase response
const { data, error } = await supabase.from("items").select("*");
if (error) throw error;
const parsed = z.array(ItemSchema).parse(data);

Anti-Patterns

Anti-PatternCorrect Approach
getSession() for auth validationUse getUser() - validates JWT
auth.uid() directly in RLSWrap in (select auth.uid())
Module-scope Supabase clientCreate inside request handler
Service role key on clientServer-only, never expose
postgres_changes for chatUse broadcast channels
Caching auth responses ('use cache')Keep auth routes dynamic

CLI Quick Reference

# Setup
supabase login
supabase link --project-ref <ref>

# Type generation
supabase gen types typescript --project-id <ref> --schema public > database.types.ts

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

# Edge Functions
supabase functions serve           # Local development
supabase functions deploy <name>   # Deploy to production

Reference Documentation

Navigate to detailed guides based on task:

Core Setup & Operations

  • [CLI Mastery](references/cli-mastery.md): Complete CLI workflow, type generation, migrations
  • [Database](references/database.md): Migrations, pgvector, functions, extensions
  • [Vercel Deployment](references/vercel-deployment.md): Integration, pooling, env vars

Authentication & Security

  • [Auth SSR](references/auth-ssr.md): @supabase/ssr setup, PKCE, OAuth, middleware
  • [RLS Cookbook](references/rls-cookbook.md): Policy patterns, team access, storage RLS

Data & Features

  • [Storage](references/storage.md): Buckets, uploads, transformations, signed URLs
  • [Realtime](references/realtime.md): Broadcast, presence, authorization
  • [AI Vectors](references/ai-vectors.md): pgvector, embeddings, semantic search
  • [Edge Functions](references/edge-functions.md): Deno runtime, deployment, CORS

Templates

Migration Template

-- supabase/migrations/YYYYMMDDHHmmss_description.sql

-- Enable required extensions
create extension if not exists "uuid-ossp";

-- Create table with RLS
create table public.items (
  id uuid primary key default uuid_generate_v4(),
  user_id uuid not null references auth.users(id) on delete cascade,
  title text not null,
  created_at timestamptz not null default now()
);

-- Enable RLS (mandatory)
alter table public.items enable row level security;

-- Policies
create policy "Users view own items"
on public.items for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Users insert own items"
on public.items for insert
to authenticated
with check ((select auth.uid()) = user_id);

-- Indexes
create index items_user_id_idx on public.items(user_id);

comment on table public.items is 'User items with RLS';

Edge Function Template

// supabase/functions/my-function/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "npm:@supabase/supabase-js@2";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "POST, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Authorization",
};

Deno.serve(async (req) => {
  if (req.method === "OPTIONS") {
    return new Response(null, { headers: corsHeaders });
  }

  try {
    const authHeader = req.headers.get("Authorization");
    const supabase = createClient(
      Deno.env.get("SUPABASE_URL")!,
      Deno.env.get("SUPABASE_ANON_KEY")!,
      { global: { headers: { Authorization: authHeader! } } }
    );

    const { data: { user } } = await supabase.auth.getUser();
    if (!user) {
      return new Response(JSON.stringify({ error: "Unauthorized" }), {
        status: 401,
        headers: { ...corsHeaders, "Content-Type": "application/json" },
      });
    }

    const body = await req.json();
    // Process request...

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

GitHub Actions Type Generation

# .github/workflows/supabase-types.yml
name: Generate Supabase Types
on:
  push:
    paths: ["supabase/migrations/**"]
    branches: [main]

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: supabase/setup-cli@v1
      - run: |
          supabase gen types typescript \
            --project-id ${{ secrets.SUPABASE_PROJECT_REF }} \
            --schema public \
            > src/lib/supabase/database.types.ts
        env:
          SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      - uses: peter-evans/create-pull-request@v5
        with:
          commit-message: "chore: update database types"
          title: "Update Supabase Database Types"
          branch: update-db-types

Related skills

FAQ

Should I use getSession() or getUser()?

Use getUser() because it validates the JWT with the auth server; getSession() only reads from the cookie and can be spoofed.

How should RLS reference the user id?

Wrap it in a subquery, using (select auth.uid()) = user_id, to prevent multiple auth.uid() calls.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.