# Supabase

> Use when the project uses Supabase — Postgres with RLS, Supabase Auth, Storage buckets, Realtime, Edge Functions — including JWT/RLS permission errors and multi-tenant models.

- Skill: `muxammadmamajonov/supabase` (Agent Skill)
- Install (CLI): `npx skillmds@latest add muxammadmamajonov/supabase`
- Raw SKILL.md: https://api.skillmd.com/api/skills/muxammadmamajonov/supabase/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: muxammadmamajonov (https://skillmd.com/u/muxammadmamajonov)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/muxammadmamajonov/supabase

---


# Supabase Skill

## When to use

- Setting up or migrating a project to Supabase (Postgres + RLS + Auth + Storage + Realtime + Edge Functions)
- Designing Row Level Security (RLS) policies
- Writing or debugging Supabase Edge Functions (Deno/TypeScript)
- Configuring Supabase Auth (OAuth providers, magic links, custom claims)
- Querying with `supabase-js`, PostgREST REST API, or direct SQL
- Diagnosing JWT claim issues, RLS infinite recursion, or N+1 query patterns via PostgREST

---

## Workflow

1. **Understand data shape first** — sketch entity relationships and access patterns before touching the database. Identify which rows each user role may read/write.
2. **Create tables with `supabase migration new`** — never ALTER tables manually in the Supabase Dashboard in production; always use versioned SQL migrations in `supabase/migrations/`.
3. **Enable RLS immediately** — `ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;` as part of the same migration that creates the table. A table with RLS disabled is publicly readable via PostgREST unless blocked at the API gateway.
4. **Write the minimal RLS policies needed** — one policy per operation (SELECT, INSERT, UPDATE, DELETE) per role. Use `auth.uid()` and `auth.jwt()` for user-scoped checks; use a `role` column or a separate `memberships` join table for team/org scoping.
5. **Test policies with `SET LOCAL role = authenticated; SET LOCAL "request.jwt.claims" = '{"sub":"<uuid>"}'`** in a `BEGIN … ROLLBACK` block before deploying.
6. **Add DB indexes** for every FK column and every column referenced inside an RLS policy `USING` clause — the policy runs per-row and can cause sequential scans without an index.
7. **Use generated columns or DB functions for computed fields** rather than fetching raw rows and computing in application code.
8. **Edge Functions**: scaffold with `supabase functions new <name>`, keep business logic thin (validate input → call DB or external service → return JSON), and use `supabase.auth.getUser()` from the service-role client only for admin paths.
9. **Realtime**: enable only the tables and events (INSERT / UPDATE / DELETE) actually needed. Filter subscriptions on the client side with `.eq('user_id', userId)` to avoid broadcasting rows the subscriber cannot see via RLS.
10. **Before production**: run `supabase db lint` and check the Supabase Dashboard → Advisors → Security for exposed tables and missing indexes.

---

## Standards

### Do
- Store secrets in Supabase Vault (`vault.secrets`) or environment variables for Edge Functions — never in table columns.
- Use `service_role` key only in Edge Functions or server-side code that runs in a trusted environment; never ship it to client bundles.
- Prefer `supabase-js` v2's typed client generated by `supabase gen types typescript`.
- Use `SECURITY DEFINER` functions sparingly and only when escalating privilege is intentional (e.g., looking up another user's public profile); always set `search_path = ''` inside them.
- Name migrations with the pattern `YYYYMMDDHHMMSS_<description>.sql`.
- Pin `supabase-js` and Deno SDK versions in `deno.json` / `package.json`.

### Do not
- Do not call `supabase.auth.admin.*` from client-side code.
- Do not use the `public` schema as a catch-all; group tables into schemas (`app`, `billing`, `internal`) where the project grows beyond ~10 tables.
- Do not disable RLS on a table that is accessible via the PostgREST API (anon or authenticated role).
- Do not write RLS policies that JOIN to the same table recursively without a `security barrier` view as the intermediary.
- Do not use `*` in PostgREST selects when only a few columns are needed — over-fetching triggers RLS checks on unused columns and inflates response size.

---

## Common mistakes to avoid

| Mistake | Consequence | Fix |
|---|---|---|
| Forgetting `ENABLE ROW LEVEL SECURITY` | Table is fully public via API | Add to migration immediately after `CREATE TABLE` |
| RLS policy referencing `auth.uid()` on a table without a `user_id` index | Full table scan on every request | `CREATE INDEX ON table(user_id)` |
| Using `service_role` key in a Next.js/React bundle | Full DB bypass exposed to users | Move to a server action or Edge Function |
| Circular RLS: policy on `profiles` references `memberships`, which has a policy referencing `profiles` | Infinite recursion → 500 error | Break the cycle with a `SECURITY DEFINER` helper function |
| Deploying Edge Functions that import large npm packages via esm.sh | Cold-start latency spikes | Bundle only what is needed; prefer Deno std lib |
| Not testing migrations locally before push | Breaking schema changes in prod | Use `supabase db reset` locally; test in a branch project |
| Storing user PII in Realtime broadcast payloads | Data leaks to subscribers | Filter columns in the Realtime publication or use server-side filtered channels |

---

## Output format

Migrations are plain `.sql` files in `supabase/migrations/`. Example structure:

```
supabase/
  migrations/
    20240601120000_create_posts.sql
    20240601120001_rls_posts.sql
  functions/
    send-notification/
      index.ts
      deno.json
  seed.sql
  config.toml
```

Edge Function response shape:
```ts
return new Response(JSON.stringify({ data, error: null }), {
  headers: { "Content-Type": "application/json" },
  status: 200,
});
```

---

## Related checklists
- `.claude/checklists/security.md`
- `.claude/checklists/database.md`
- `.claude/checklists/launch.md`

## Related agents
- `.claude/agents/engineering/backend-engineer.md`
- `.claude/agents/quality/security-auditor.md`
- `.claude/agents/core/system-analyst.md`

