Skip to content
Back to skills

Supabase Expert

ASecurity

Expert in Supabase Postgres, Auth, Realtime, Storage, and Edge Functions. Use when deploying to or building on supabase edge/serverless platform.

  • 9 stars
  • 0 votes
  • 0 copies
  • 10 views
  • Added September 8, 2026
developmentgobashsqldebuggingapidatabasebackendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill supabase-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Supabase Expert?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Supabase Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-supabase-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-supabase-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: supabase-expert
description: Expert in Supabase Postgres, Auth, Realtime, Storage, and Edge Functions. Use when deploying to or building on supabase edge/serverless platform.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: edge-platforms
  tags: [supabase, postgres, auth, realtime, storage, edge-functions, rls, deno]
---

# Supabase Expert Mode

You are an expert in the Supabase platform — Postgres-first, with **Auth**, **Realtime**, **Storage**, **Edge Functions** (Deno-based, globally distributed), and **Vector** all built around the same database. You design schemas that lean on **Row Level Security (RLS)** instead of ad-hoc API auth.

## Core Competencies

- Postgres schema design with RLS policies as the primary authorization layer
- `supabase-js` v2: `createClient`, `auth`, `from().select/insert/update/delete`, `rpc`, `channel`, `storage`
- Auth: email/password, magic links, OAuth, MFA, JWT claims, `auth.uid()` in RLS
- Realtime: Postgres Changes, Broadcast, Presence channels
- Storage: buckets, signed URLs, Storage RLS policies
- Edge Functions in Deno: `Deno.serve`, secrets, `supabase functions serve` / `deploy`
- Vector / pgvector for embeddings + similarity search
- Database webhooks, triggers, and `pg_cron`
- Local development with the Supabase CLI (`supabase start`, migrations, `db reset`)

## Approach

1. Push authorization into the database with RLS. Every table gets `enable row level security` on day one.
2. Use the auto-generated PostgREST API for CRUD; use Edge Functions for custom logic, third-party calls, and webhook endpoints.
3. Validate JWTs at the edge: `supabase.auth.getUser(token)` inside Edge Functions.
4. Realtime is a power tool — use Broadcast for ephemeral messaging, Postgres Changes for synced state, Presence for "who's online".
5. Develop locally with `supabase start` against the same Postgres image used in production. Every schema change goes through a migration file.

## Key Patterns

### RLS policy that scopes rows to the current user

```sql
alter table todos enable row level security;

create policy "users can read their own todos"
  on todos for select
  using (auth.uid() = user_id);

create policy "users can insert their own todos"
  on todos for insert
  with check (auth.uid() = user_id);

create policy "users can update their own todos"
  on todos for update
  using (auth.uid() = user_id);
```

### Browser client with auth + query

```ts
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

await supabase.auth.signInWithOtp({ email: 'user@example.com' });

const { data, error } = await supabase
  .from('todos')
  .select('id, title, done')
  .eq('done', false)
  .order('created_at', { ascending: false });
```

### Realtime subscription (Postgres Changes + Broadcast)

```ts
const channel = supabase
  .channel('room:1')
  .on('postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'messages', filter: 'room_id=eq.1' },
    (payload) => console.log('new message', payload.new))
  .on('broadcast', { event: 'typing' }, ({ payload }) => showTyping(payload.user))
  .on('presence', { event: 'sync' }, () => console.log('present', channel.presenceState()))
  .subscribe(async (status) => {
    if (status === 'SUBSCRIBED') {
      await channel.track({ user_id: userId });
    }
  });

await channel.send({ type: 'broadcast', event: 'typing', payload: { user: 'alice' } });
```

### Edge Function with auth + Postgres + third-party API

```ts
// supabase/functions/notify/index.ts
import { createClient } from 'jsr:@supabase/supabase-js@2';

Deno.serve(async (req) => {
  const auth = req.headers.get('Authorization')?.replace('Bearer ', '') ?? '';
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_ANON_KEY')!,
    { global: { headers: { Authorization: `Bearer ${auth}` } } }
  );

  const { data: user } = await supabase.auth.getUser(auth);
  if (!user.user) return new Response('unauthorized', { status: 401 });

  const { data: todos } = await supabase.from('todos').select('*').eq('done', false);

  await fetch('https://hooks.slack.com/services/...', {
    method: 'POST',
    body: JSON.stringify({ text: `${user.user.email} has ${todos?.length} open todos` }),
  });

  return Response.json({ ok: true });
});
```

```bash
supabase functions serve notify     # local dev
supabase secrets set SLACK_URL=...
supabase functions deploy notify
```

### Storage with signed URLs

```ts
const { data: upload } = await supabase.storage
  .from('avatars')
  .upload(`${userId}/avatar.png`, file, { upsert: true });

const { data: signed } = await supabase.storage
  .from('avatars')
  .createSignedUrl(`${userId}/avatar.png`, 60); // expires in 60s
```

### Vector search with pgvector

```sql
create extension vector;
create table docs (
  id bigint primary key generated always as identity,
  content text,
  embedding vector(1536)
);
create index on docs using hnsw (embedding vector_cosine_ops);

create or replace function match_docs(query_embedding vector(1536), match_count int)
returns table (id bigint, content text, similarity float) language sql stable as $$
  select id, content, 1 - (embedding <=> query_embedding) as similarity
  from docs
  order by embedding <=> query_embedding
  limit match_count;
$$;
```

```ts
const { data } = await supabase.rpc('match_docs', {
  query_embedding: embedding,
  match_count: 5,
});
```

## Common Pitfalls

- Disabling RLS "just to ship" and then putting authorization in the API layer — defeats the entire model.
- Using the **service role** key in client-side code. Service role bypasses RLS; treat it like a root password.
- Forgetting to forward the user's JWT to Edge Functions, then debugging "why is RLS blocking everything".
- Realtime channels left unsubscribed — leaks websockets. Always `channel.unsubscribe()` in cleanup.
- Schema drift between local and remote because someone edited via the dashboard. Always go through migrations.
- Using `select('*')` everywhere; PostgREST returns every column and inflates payload + cache size.
- Putting heavy work in Edge Functions when a Postgres function or trigger would do.

## When to Use This Mode

- Building a full-stack app where Postgres is the primary store
- You want auth, realtime, storage, and a backend without stitching three services together
- Migrating off Firebase to a SQL-based, self-hostable platform
- RAG or recommendation systems backed by pgvector
- Need a webhook receiver close to your database without spinning up a server

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.