| name | supabase-remote |
| description | Manage a Supabase project from the CLI — list projects, run SQL files (with post-run row-count verification), run inline queries, audit RLS coverage, and generate TypeScript types. Wraps the Supabase Management API + the local `supabase` CLI so agents don't have to hand-parse the untrusted-data envelope. |
| when_to_use | Trigger on phrases like "run this SQL on Supabase", "apply the seed file", "audit RLS", "which tables don't have RLS", "regenerate types", "list Supabase projects", "which project am I linked to", "resolve project ref". |
| allowed-tools | Bash(supabase-remote:*) Bash(python3 *supabase-remote*:*) |
supabase-remote
Stdlib-only Python 3 CLI wrapping the Supabase Management API + the local supabase CLI. One file, no pip installs. Lives at ${CLAUDE_SKILL_DIR}/../../bin/supabase-remote; the plugin auto-adds bin/ to PATH.
Generic Supabase ops only. Domain-specific helpers (member lookups, custom comms tails, app-specific onboarding queries) belong in the consuming project's own repo and should shell out to sql-inline here for the SQL execution.
When to reach for this
- User wants to apply a SQL file to a Supabase project and confirm it worked.
- User wants to check RLS coverage before shipping.
- User wants to regenerate the TypeScript types file from the live schema.
- User asks "which project am I linked to" / "list my Supabase projects".
- User wants a one-shot read query against the linked project.
Do NOT use for auth user management, storage buckets, or edge function deploys — out of scope. For those, use the supabase CLI directly or the dashboard.
When NOT to use this — fall back to supabase CLI or Management API directly
This wrapper's scope is deliberately narrow: projects list/resolve/current, SQL execution (file + inline with write-guard), RLS audit, and TypeScript gen-types. Anything outside that set is unwrapped on purpose. If the user's request obviously needs a capability this tool doesn't expose, skip supabase-remote and reach for the supabase CLI or curl https://api.supabase.com/v1/... directly — both are already authed on their machine via SUPABASE_ACCESS_TOKEN.
Specific cases where you should use supabase ... (or curl against the Management API) directly, not supabase-remote:
- Auth user management — creating, inviting, banning, deleting users, resetting passwords, managing MFA factors. →
curl https://api.supabase.com/v1/projects/<ref>/auth/users or the GoTrue admin API.
- Storage buckets and objects — creating buckets, setting policies, uploading/downloading files, signed URLs. →
supabase storage subcommands or the Storage REST API.
- Edge function deploys and invocation —
deploy, serve, invoke, managing function secrets. → supabase functions deploy <name> / supabase functions invoke.
- Database migrations /
db push / schema diff — real migration workflows belong in supabase/migrations/ and should go through supabase db push, supabase db diff, or supabase migration new. sql <file> here is for seeds and one-offs, not replicable schema changes.
- Project provisioning, pausing, restoring, or branch management — creating projects, pausing/restoring, managing preview branches. →
POST /v1/projects, POST /v1/projects/<ref>/pause, or the dashboard.
- Vault/secrets, realtime config, network restrictions, custom domains — anything in Management API areas this CLI doesn't model. →
curl https://api.supabase.com/v1/projects/<ref>/... with Authorization: Bearer $SUPABASE_ACCESS_TOKEN.
Don't get stuck in a loop. If supabase-remote doesn't have a subcommand that obviously fits the request, don't try to coax it out with creative flag combinations — switch straight to supabase or curl against https://api.supabase.com/v1/*. The wrapper exists to make a handful of common read+SQL ops safer and more agent-friendly, not to be the only way to talk to Supabase.
Configure
Layered config, highest precedence first:
--env-file <path>
$CWD/.env.local / $CWD/.env (walked up from cwd)
~/.agent-plus/.env
- Shell environment (including Claude Code settings)
Project .env files override the shell. Only SUPABASE_* keys are picked up.
SUPABASE_ACCESS_TOKEN=sbp_...
SUPABASE_PROJECT_REF=abcdefghijklmnopqrst
SUPABASE_DB_URL=postgres://...
Get a personal access token at https://supabase.com/dashboard/account/tokens. This is distinct from a project's anon/service keys.
Commands
supabase-remote projects list [--json]
supabase-remote projects resolve <name-or-ref>
supabase-remote projects current [--format text|json]
supabase-remote sql <file> [--project NAME] [--verify-rows N] [--json]
supabase-remote sql-inline "<query>" [--project NAME] [--write] [--json]
supabase-remote rls-audit [--project NAME] [--format table|json]
supabase-remote gen-types <target.ts> [--project NAME] [--schema NAME]...
supabase-remote whoami [--json]
Add --debug at the top level to print the underlying HTTP and shell calls (access token is scrubbed). Run supabase-remote --version to print the plugin version and exit — handy for self-diagnosis without needing auth.
All list-shaped commands support --json. rls-audit prefers --format json; --json is kept as a deprecated alias.
Tool meta in every JSON payload
Every JSON object this tool emits carries a top-level tool: { name, version }
field, read at runtime from plugin.json. Agents can spot version drift from
the output alone — no extra call needed. List-shaped outputs (e.g. projects list --json, rls-audit --format json, sql rows) stay as plain JSON arrays
because injecting into a list would change the shape.
Piping to jq
All JSON output is single-document, so jq works directly:
supabase-remote projects list --json | jq '.[] | {name, ref: .id}'
supabase-remote rls-audit --format json | jq '[.[] | select(.rls == false)]'
supabase-remote projects current --format json | jq -r .ref
projects current
Shows which project supabase-remote would use if you ran a command right now, and where that value came from:
$ supabase-remote projects current
ref: abcdefghijklmnopqrst
name: myproj
source: SUPABASE_PROJECT_REF environment variable
$ supabase-remote projects current --format json
{
"tool": { "name": "supabase-remote", "version": "0.2.0" },
"resolved": true,
"ref": "abcdefghijklmnopqrst",
"name": "myproj",
"source": "env",
"raw_input": "abcdefghijklmnopqrst"
}
source is one of:
argument — came from --project on the current invocation
env — came from SUPABASE_PROJECT_REF
linked — read from ./supabase/.temp/project-ref (written by supabase link)
Use this before running a destructive SQL apply when the active project isn't obvious.
Projects are resolved by name
--project accepts either the project name (case-insensitive substring) or the 20-char ref. No need to copy refs around.
supabase-remote sql migration.sql --project myproj
supabase-remote sql migration.sql --project abcdefghijklmnopqrst
If --project is omitted, the CLI uses SUPABASE_PROJECT_REF, then falls back to ./supabase/.temp/project-ref (written by supabase link).
Typical flows
1. Apply a seed SQL file with row verification
supabase-remote sql migrations/seed-content.sql \
--project myproj \
--verify-rows 12
The CLI scans the SQL for the final insert into <table> / update <table>, runs the file, then re-queries select count(*) from <table>. Mismatch → exit 2 with a clear error. No mismatch → prints verify: content: 12 row(s) — OK.
Under the hood this calls supabase db query --linked -f <path> --output json --agent no, which avoids the "untrusted data" envelope the CLI wraps around output when it detects an agent. If SUPABASE_DB_URL is set, it uses psql directly instead.
2. Audit RLS across all tables
supabase-remote rls-audit --project myproj
TABLE RLS POLICIES STATUS
applications enabled 3 OK
brain_calendar enabled 2 OK
new_secret_table DISABLED 0 NO RLS, NO POLICIES — FIX
users enabled 3 OK
...
Queries pg_tables + pg_policies via sql-inline internally. Flags any table in public with RLS off, RLS on but no policies, or both. Run this before shipping any new table.
3. Regenerate TypeScript types into your project
supabase-remote gen-types packages/db/types.ts --project myproj
supabase-remote gen-types packages/db/types.ts \
--project myproj \
--schema public \
--schema auth
Wraps supabase gen types typescript --project-id <ref> and writes atomically (via a .tmp sibling + os.replace). Parent directory must already exist — the CLI refuses to mkdir implicitly to avoid dumping a types file in the wrong place.
--schema accepts any Postgres identifier ([A-Za-z_][A-Za-z0-9_]*). Suspicious values are rejected before supabase is invoked, so you can't accidentally inject extra CLI flags via the schema name.
Inline SQL safety
sql-inline refuses to run a query starting with INSERT, UPDATE, DELETE, DROP, TRUNCATE, ALTER, CREATE, GRANT, or REVOKE unless --write is passed. Pattern:
supabase-remote sql-inline "select count(*) from events where created_at > now() - interval '1 day'"
supabase-remote sql-inline "delete from rsvps where member_id = '...'" --write
For anything substantial, prefer sql <file> so the query lands in source control.
Gotchas
- The "untrusted data" envelope.
supabase db query in agent mode wraps output in a JSON envelope with a warning preamble. We pass --agent no to turn that off. If a future CLI version ignores that flag, we still scan for { "data": [...] } / { "rows": [...] } shapes and unwrap them.
--verify-rows won't catch partial writes. It just asserts the final row count. For stronger guarantees use real migrations (supabase migration new ...) and run them through CI.
- No
psycopg2, no requests. Stdlib only, so SQL requires either the supabase CLI or psql on PATH. If neither is available the CLI errors out immediately.
- Access token scrubbing. Any stderr/error path with
--debug on scrubs $SUPABASE_ACCESS_TOKEN (and any sbp_... token pattern) from the output. Still — don't paste --debug transcripts into untrusted places without a second look.
Troubleshooting
SUPABASE_ACCESS_TOKEN is not set: Create a token at https://supabase.com/dashboard/account/tokens and drop it in $CWD/.env (project) or ~/.claude/settings.json (global).
No project matching 'xyz': Run supabase-remote projects list to see the names your token can see. Names are case-insensitive substring matches; if you have two projects with overlapping names, use the full ref or a more specific substring.
supabase db query failed: Usually means the CLI isn't linked to any project, or your SUPABASE_ACCESS_TOKEN doesn't have access to the target project. Try supabase link --project-ref <ref> first, or set SUPABASE_DB_URL to bypass the CLI entirely.