insert returns 200 with PGRST116 — no row written
appears when:User submits a form, the UI shows a success toast, and the row never appears in the Supabase Table Editor
database saves no error no row
The API lied — 200 with an empty array. Four likely causes: silent RLS block, transaction rollback, wrong client key, or schema drift.
.select() that filters the returned row back out — PGRST116 with an empty array looks like success. Read the Postgres log for the real 42501 error, then write an INSERT policy with WITH CHECK (auth.uid() = user_id) and a matching SELECT policy.Quick fix for database saves no error no row
01-- Minimum policy set for a per-user table02-- INSERT policy with WITH CHECK (NOT USING — USING is ignored for INSERT)03create policy "users insert own rows"04on public.tasks05for insert06to authenticated07with check (auth.uid() = user_id);08 09-- SELECT policy so chained .select() returns the row back10create policy "users read own rows"11on public.tasks12for select13to authenticated14using (auth.uid() = user_id);15 16-- Default user_id from the authenticated session17alter table public.tasks18alter column user_id set default auth.uid();Deeper fixes when the quick fix fails
01 · Stop treating PGRST116 as harmless on a write path
01const { data, error } = await supabase02 .from("tasks")03 .insert(row)04 .select()05 .single();06 07if (error) {08 console.error("insert failed", error.code, error.message);09 throw error;10}11if (!data) {12 // PGRST116 + no data = RLS SELECT policy is missing13 throw new Error("insert returned no row — check RLS SELECT policy");14}02 · Check for BEFORE INSERT triggers that silently cancel
A BEFORE INSERT trigger that returns NULL cancels the insert without raising. List triggers with select tgname from pg_trigger where tgrelid = 'public.tasks'::regclass;. Review each trigger body.
03 · Rehydrate the session on the server with cookies
01// app/api/tasks/route.ts — proper server client with cookies02import { createServerClient } from "@supabase/ssr";03import { cookies } from "next/headers";04 05export async function POST(req: Request) {06 const cookieStore = await cookies();07 const supabase = createServerClient(08 process.env.NEXT_PUBLIC_SUPABASE_URL!,09 process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,10 {11 cookies: {12 getAll: () => cookieStore.getAll(),13 setAll: () => {},14 },15 }16 );17 18 // Now the client is authenticated — auth.uid() resolves on the server19 const body = await req.json();20 const { data, error } = await supabase.from("tasks").insert(body).select().single();21 if (error) return Response.json({ error }, { status: 400 });22 return Response.json(data);23}Why AI-built apps hit database saves no error no row
The Supabase-js client returns a success response even when the insert was filtered out. A call like supabase.from("tasks").insert(row).select().single() runs an INSERT followed by a SELECT. If RLS allows (or blocks) the insert and then the follow-up SELECT returns zero rows, the client surface shows no error and PGRST116 that many AI-generated handlers ignore. The toast fires, the user believes the save worked, and the Table Editor is empty.
Lovable, Bolt, and Base44 generate optimistic UI by default. The client updates local state before the server responds, so the missing row is invisible until a reload. By the time the user refreshes, they assume the bug is caching rather than a write-path failure. The most common actual cause is an INSERT policy missing WITH CHECK, or a SELECT policy missing entirely so the returned row cannot be read back.
The second cause is silent transaction rollback. A database function wrapping the insert plus a trigger can roll back when the trigger fails. PostgREST may have already sent the 200 response header. An ORM like Prisma hides the rollback behind a caught exception that re-throws as a generic error. The fix is always the same: read the Postgres log, not the client response. The log is source of truth.
database saves no error no row by AI builder
How often each AI builder ships this error and the pattern that produces it.
| Builder | Frequency | Pattern |
|---|---|---|
| Lovable | Every Supabase scaffold | INSERT without WITH CHECK; SELECT policy missing |
| Bolt.new | Common | Ignores PGRST116 in success handler |
| v0 | Common | Uses anon client on server without cookie passthrough |
| Cursor | Sometimes | Ships BEFORE INSERT triggers that return NULL on validation |
| Base44 | Rare | Schema drift between preview branch and production project |
Related errors we fix
Stop database saves no error no row recurring in AI-built apps
- →Never treat PGRST116 as success — assert `data` is present after every insert.
- →Pair every INSERT policy with a matching SELECT policy for the same role.
- →Set the ownership column default to `auth.uid()` so the client cannot forget it.
- →Write a pgTAP test that asserts cross-tenant inserts fail with 42501.
- →Log every Supabase error.code into APM so silent 116s become visible.
Still stuck with database saves no error no row?
database saves no error no row questions
Why does my Supabase insert return 200 but no row appears?+
Can a transaction rollback cause a silent save with no row?+
Does using the wrong Supabase client key cause this?+
Why is the row missing only in production?+
How long does diagnosis take?+
Ship the fix. Keep the fix.
Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.
Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.