supabase-migrations
Skill0 starsChanged 4 months agoMITView on GitHub
Write and review Postgres migrations for a self-hosted Supabase project — numbering, RLS policies, grants, idempotency and the PostgREST schema reload. Use when adding a table, changing a column, writing an RPC, or reviewing a migration before it runs against production.
- Deletes or force-pushes
1 other project uses a version of this.
Adopted by
Most recently taken up by
- matthull/my-skillstheir own version, first seen Oct 20, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- matthull/my-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository9 other files it gives its agents.
- brand-voiceSkill
- document-extractionSkill
- image-genSkill
- linkedin-carouselSkill
- nextjs-conventionsSkill
- og-generatorSkill
- pharosSkill
- seo-auditSkill
- skill-distributionSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 19
Take the next free number: Never reuse one, never renumber an applied migration.
- Distinctive: line 129
Read the whole file top to bottom, out of the diff.
- Distinctive: line 133
Run it against a scratch database first if one exists.
- Distinctive: line 134
Never run it as part of the same step that writes it.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Supabase migrations
Self-hosted Supabase fails differently from managed Supabase. The failures are quiet: the migration succeeds, the API returns an empty array, and nothing anywhere reports an error. This skill is the checklist that catches those before they ship.
When to use this
Writing a migration, reviewing one, or debugging "the table exists but the API says it does not".
The order that matters
Take the next free number. Never reuse one, never renumber an applied migration.
ls migrations/ | sort -V | tail -1- Write the DDL idempotently.
create table if not exists,add column if not exists,drop policy if existsbeforecreate policy. A migration that cannot be run twice will eventually be run twice. - Grant, then policy, then enable. Row Level Security without a grant returns permission-denied; a grant without RLS exposes every row. Both, in that order.
End with the schema reload.
notify pgrst, 'reload schema';This is the one that gets forgotten. PostgREST caches the schema at boot. Without this line the table exists in Postgres and does not exist over the API, and every symptom points at your client code.
Verify against the database, not against the file.
bash scripts/verify.sh <table_name>
Template
-- 00NN_<what_it_does>.sql
-- <one line: why this exists>
create table if not exists public.thing (
id uuid primary key default gen_random_uuid(),
slug text unique not null,
status text not null default 'draft'
check (status in ('draft', 'published', 'archived')),
payload jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists thing_status_idx on public.thing (status)
where status = 'published';
alter table public.thing enable row level security;
grant select on public.thing to anon, authenticated;
grant all on public.thing to service_role;
drop policy if exists "thing: public reads published" on public.thing;
create policy "thing: public reads published"
on public.thing for select
to anon, authenticated
using (status = 'published');
drop policy if exists "thing: service_role full access" on public.thing;
create policy "thing: service_role full access"
on public.thing for all
to service_role
using (true) with check (true);
notify pgrst, 'reload schema';RPCs
A function the API can call needs security definer only when it must bypass RLS, and
then it needs a pinned search_path or it is an escalation path:
create or replace function public.increment_counter(p_slug text, p_kind text)
returns void
language plpgsql
security definer
set search_path = public
as $$
begin
update public.thing
set payload = jsonb_set(
payload, array['counts', p_kind],
to_jsonb(coalesce((payload->'counts'->>p_kind)::int, 0) + 1)),
updated_at = now()
where slug = p_slug;
end;
$$;
revoke all on function public.increment_counter(text, text) from public;
grant execute on function public.increment_counter(text, text) to anon, authenticated;revoke ... from public first, then grant to the roles that need it. Creating a function
grants execute to public by default, which is almost never what you want for a
security definer.
Failure modes, and what they actually mean
| Symptom | Cause |
API returns [], table has rows | RLS on, no policy for that role. Not a query bug |
| API returns "relation does not exist" | Missing notify pgrst, 'reload schema' |
| Works as service_role, not as anon | Grant present, policy missing (or the reverse) |
| Insert silently drops rows | Policy has using but no with check |
| Migration passes locally, fails in prod | Different extension set. Check gen_random_uuid |
Before it runs against production
- Read the whole file top to bottom, out of the diff.
- Confirm nothing is destructive: no bare
drop table, noalter column ... typewithout ausing, nodelete fromwithout awhere. - Confirm it ends with the notify line.
- Run it against a scratch database first if one exists.
- Never run it as part of the same step that writes it.
Files
scripts/verify.sh— post-migration verification: table exists, RLS on, policies present, and the anon role can actually read what it should.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

