afterbuild/ops
ERR-483/Supabase · RLS
ERR-483
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.

Last updated 17 April 2026 · 6 min read · By Hyder Shah
Direct answer
database saves no error no row is almost always RLS blocking INSERT combined with a chained .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

supabase/migrations/rls_tasks.sql
sql
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();
Run in Supabase SQL editor — creates the two policies and the default required for silent-save fixes

Deeper fixes when the quick fix fails

01 · Stop treating PGRST116 as harmless on a write path

lib/db/insert-task.ts
typescript
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

app/api/tasks/route.ts
typescript
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.

AI builder × database saves no error no row
BuilderFrequencyPattern
LovableEvery Supabase scaffoldINSERT without WITH CHECK; SELECT policy missing
Bolt.newCommonIgnores PGRST116 in success handler
v0CommonUses anon client on server without cookie passthrough
CursorSometimesShips BEFORE INSERT triggers that return NULL on validation
Base44RareSchema drift between preview branch and production project

Related errors we fix

Stop database saves no error no row recurring in AI-built apps

Still stuck with database saves no error no row?

Emergency triage · $299 · 48h turnaround
We restore service and write the root-cause report.
start the triage →

database saves no error no row questions

Why does my Supabase insert return 200 but no row appears?+
The most common cause is Row Level Security with no INSERT policy combined with a chained .select() call. Supabase-js returns an array with zero elements, your success handler runs because status is 200, and nothing was written. Run the same insert in the SQL editor as the authenticated role to see the real 42501 error. Adding a WITH CHECK policy fixes it in under a minute.
Can a transaction rollback cause a silent save with no row?+
Yes. If your insert is wrapped in a database function or explicit transaction and something later in the transaction raises, Postgres rolls everything back. The client may have already received the response because the framework buffered it. Check Postgres logs for ROLLBACK entries near the failure. Drizzle, Prisma, and raw pg all exhibit this if exception handling is wrong.
Does using the wrong Supabase client key cause this?+
It can. If server code accidentally uses the anon client without a session token, the insert runs as the anon role. RLS may allow the insert, return 200, but only the authenticated role can see the row in subsequent reads. The row exists but nobody can read it. Verify with a service_role query to confirm the row landed, then fix the client to use the signed-in session.
Why is the row missing only in production?+
Three patterns: RLS policies present in staging were never migrated to production; the production environment points at a different schema than the one you are inspecting; or the production deploy switched from service_role to anon key and RLS silently blocks. Confirm schema and key first — Supabase Dashboard → Settings → API shows both.
How long does diagnosis take?+
Fifteen minutes if you have dashboard access and the error reproduces on demand. Ten of those are log inspection; the rest is writing or amending the policy. Emergency Triage is $299 with 48-hour turnaround if you are blocked from debugging production directly, or if the save works for some users but not others and you cannot reproduce consistently.
Next step

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.

About the author

Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.

Sources