supabase
Set up or extend Supabase schema, RLS policies, migrations, typed clients, and external-auth sync. Use when integrating Supabase or repairing data access.
- Category
- devops
- Package
- supabase/SKILL.md
- License
- MIT
- Author
- @tushaarmehtaa
- Tags
- supabasepostgresrlsauthmigrationstypescript
Install
Swipe for more runtimes.
Codex
Skills directory: ~/.codex/skills
Install globally
npx skills add tushaarmehtaa/tushar-skills --skill supabase -g -a codex -yInvoke
$supabase or /skillsYou can also describe the task naturally; runtimes may select the skill from its description.
Required access
Claude Code
Skills directory: ~/.claude/skills
Install globally
npx skills add tushaarmehtaa/tushar-skills --skill supabase -g -a claude-code -yInvoke
/supabaseYou can also describe the task naturally; runtimes may select the skill from its description.
Required access
Cursor
Skills directory: ~/.cursor/skills
Install globally
npx skills add tushaarmehtaa/tushar-skills --skill supabase -g -a cursor -yInvoke
/supabaseYou can also describe the task naturally; runtimes may select the skill from its description.
Required access
local coding agent required
This skill requires project files, terminal commands, and network access. Uploading it to a chat app does not provide equivalent execution.
ChatGPT Skills
This workflow needs a local coding environment or capabilities that a chat-only Skills upload does not provide.
Why local agent required →Instructions
Source: SKILL.mdSet up or extend a Supabase integration from schema to type-safe queries.
Context to read
supabase/directory if it exists- Any
*.sqlfiles in the repo .env.localor.envforSUPABASE_URLandSUPABASE_ANON_KEY- Auth config (Clerk, Auth0, or Supabase Auth)
package.jsonfor@supabase/supabase-jsversion
Steps
1. Check what exists
ls supabase/ 2>/dev/null
cat supabase/config.toml 2>/dev/null
Is Supabase initialized? Are there existing migrations? Which tables exist?
2. Schema design
For each new table:
uuidprimary key withgen_random_uuid()defaultcreated_at timestamptz default now()updated_at timestamptz default now()user_idfor user-owned rows
If using an external auth provider (Clerk, Auth0), store their user ID as text — not a reference to auth.users:
user_id text not null -- clerk user_id, not uuid ref to auth.users
3. RLS policies
Enable RLS on every table that holds user data. Never leave RLS enabled with no policies — every query will fail silently and return empty results, not an error.
alter table public.your_table enable row level security;
Three canonical patterns. Pick the one that matches the table:
Pattern A — User owns row (most common: posts, files, credits, settings)
-- Supabase Auth
create policy "select own" on public.your_table for select using (user_id = auth.uid()::text);
create policy "insert own" on public.your_table for insert with check (user_id = auth.uid()::text);
create policy "update own" on public.your_table for update using (user_id = auth.uid()::text);
create policy "delete own" on public.your_table for delete using (user_id = auth.uid()::text);
-- External auth (Clerk / Auth0) — user_id is the JWT sub claim
create policy "select own" on public.your_table for select using (user_id = (auth.jwt() ->> 'sub'));
create policy "insert own" on public.your_table for insert with check (user_id = (auth.jwt() ->> 'sub'));
create policy "update own" on public.your_table for update using (user_id = (auth.jwt() ->> 'sub'));
create policy "delete own" on public.your_table for delete using (user_id = (auth.jwt() ->> 'sub'));
Pattern B — Team owns row (workspaces, shared projects)
-- Users can access rows where their user_id is in the team_members join table
create policy "select team rows" on public.your_table for select
using (
team_id in (
select team_id from team_members where user_id = (auth.jwt() ->> 'sub')
)
);
-- Repeat for insert/update/delete with same team_id check
Pattern C — Admin bypass (service role for background jobs, webhooks)
-- Webhooks and cron jobs use the service role key, which bypasses RLS entirely.
-- Never use the service role key in client-side code.
-- In Next.js route handlers that need admin access:
import { createClient } from '@supabase/supabase-js';
const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY! // server-side only, never expose
);
For each new table: generate all 4 CRUD policies (select, insert, update, delete). Don't leave gaps — a table with only a select policy will silently block all inserts.
4. Migrations
supabase db diff --file describe_the_change
supabase migration up
Always generate a named migration file. Never edit the schema directly in production. If no Supabase CLI:
npx supabase db diff --file describe_the_change
5. Generate TypeScript types
supabase gen types typescript --local > src/types/supabase.ts
# or for remote:
supabase gen types typescript --project-id your-project-id > src/types/supabase.ts
Import the generated types in every query. Do not use any for Supabase results.
6. Client setup
// lib/supabase.ts — browser client
import { createBrowserClient } from "@supabase/ssr";
import type { Database } from "@/types/supabase";
export const supabase = createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// lib/supabase-server.ts — server components / route handlers
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import type { Database } from "@/types/supabase";
export function createSupabaseServer() {
const cookieStore = cookies();
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { get: (name) => cookieStore.get(name)?.value } }
);
}
7. Output
Report:
- Tables created or modified
- RLS policies set (and which auth strategy they use)
- Migration files written
- Type generation command confirmed
- Any auth edge cases (external provider JWT claims, service role usage)