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.

  • 8 stars
  • 0 votes
  • 0 copies
  • 9 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…