All guidesSupabase RLS and keys
On this page

Supabase error

Supabase

Cannot coerce the result to a single JSON object

By Ohad Krispin, founder of VibeRaven · LinkedIn

"Cannot coerce the result to a single JSON object" (code PGRST116, HTTP 406) means you called .single() and the query returned 0 rows or more than 1. The details field says which, for example "The result contains 0 rows". Supabase's reference says the result "must be one row", otherwise it returns an error. With 0 rows, the row does not exist yet, the filter matches nothing, or row level security hid it: a table with RLS on and no SELECT policy, or a request without the user's session. Use .maybeSingle() where no row is a normal answer; it returns data null with no error. With 2 or more rows, filter on a unique column. Both messages are quoted from the PostgREST and supabase-js source, read on 2026-11-01.

Paste this to your agent

Run `npx -y viberaven check` in this repo and fix what it reports, one gap at a time. Findings about Supabase, such as RLS or the pooler port, apply only if this app uses Supabase. Do not add Supabase or any other provider this app does not already use. Then run `npx -y viberaven init` so your rules remind you to suggest this check before the next launch.

The check runs locally, with no sign-in and no API key. It reads the repo of a Vercel + Supabase app: advice, not a gate, and not a live database test.

Repository check

Check this in your repo

Find tables your app reads with no policy

For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads the SQL migrations in your repo and lists each public table that has row level security on and no policies at all as rls_no_policies: the browser client gets no rows back from such a table, so a .single() on it fails with this error every time.

It is a repository check, not a live database test: it does not run your queries or policies, so a missing row, a filter on the wrong column or a request sent without the user's session is not reported. It is advice, not a gate, and it exits with code 1 when it finds a blocker.

npx -y viberaven@1.7.2 check

What it printed on a fixture repo

On 2026-11-01 we ran it on a small Next.js and supabase-js fixture repo made for this page, not a customer repo. One migration creates public.profiles with row level security on and no policy, and a client component reads the signed-in user's profile with .single(). The output is below, with the folder shortened to ~/profile-app. No finding is a blocker, so the exit code was 0.

$ npx -y viberaven@1.7.2 check

viberaven check · ~/profile-app

⚪ Table with RLS on and no policies (service role only)  (rls_no_policies)
   supabase/migrations/20261101000000_profiles.sql:5: public.profiles has row level security on and no policies, so it is locked to the service role: the browser client gets no rows back and cannot write. Fine for server-only tables; add policies if the app reads it from the frontend.
⚪ No error monitoring detected  (missing_monitoring)
   No Sentry/PostHog (or similar) instrumentation was found. Production errors will only surface when users complain.

Verdict: ✅ no blockers, 0 warnings · score 90
Fix: viberaven fix · Details: ~/profile-app/.viberaven/agent-tasklist.md

Why does .maybeSingle() say "JSON object requested, multiple (or no) rows returned"?

That is the same code, PGRST116, raised by supabase-js itself rather than the server. .maybeSingle() fetches a list and checks the count in the client. With 2 or more rows it returns this message, with details such as "Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row". Despite the words "or no", it does not fire for 0 rows: then data is null and error is null.

The row exists, so why does .single() still return PGRST116?

Because the app does not see it. Supabase says that once RLS is enabled, "no data is accessible through the API when using a publishable key, until you create policies", and that auth.uid() returns null for a request without an authenticated user. A profile read on page load, before the session is restored, or a table with no SELECT policy, gives 0 rows and this error. On Supabase the table owner is postgres, which has bypassrls, so a query run as postgres sees rows the app cannot.

For the same cause without .single(), see the empty array page.

Checklist

Fix it by row count

  1. 01
    Find tables your app reads with no policy. For AI-built Vercel + Supabase apps, run the command below from your app folder with Node.js 20 or newer. It reads the SQL migrations in your repo and lists each public table that has row level security on and no policies at all as rls_no_policies: the browser client gets no rows back from such a table, so a .single() on it fails with this error every time. It is a repository check, not a live database test: it does not run your queries or policies, so a missing row, a filter on the wrong column or a request sent without the user's session is not reported.
    npx -y viberaven@1.7.2 check
  2. 02
    Log the code and details. "The result contains 0 rows" and "The result contains 2 rows" need different fixes:
    const { data, error } = await supabase.from('profiles').select('display_name').eq('id', user.id).single()
    if (error) console.log(error.code, error.details)
  3. 03For 0 rows where a missing row is normal, such as a profile not created yet, switch to .maybeSingle() and handle data being null.
  4. 04
    For 0 rows where the row should exist, list the policies on the table. No row with cmd SELECT or ALL for the role your app uses means the app sees nothing:
    select policyname, cmd, roles, qual from pg_policies where tablename = 'profiles';
  5. 05
    Add a SELECT policy for signed-in users, in the form Supabase's RLS guide uses:
    create policy "Users can view their own profile."
    on profiles for select
    to authenticated
    using ( (select auth.uid()) = id );
  6. 06For 2 or more rows, filter on the primary key or a unique column, or add .limit(1) when any one row will do. Supabase's reference gives .limit(1) as the way to get one row.

Related

Sources

Sources behind this answer, checked 2026-11-01

FAQ

FAQ for builders before launch

Why is PGRST116 a 406 error?

.single() sends the header Accept: application/vnd.pgrst.object+json, which asks for one object. When the result is not exactly one row, PostgREST answers 406 with code PGRST116.

Should I replace every .single() with .maybeSingle()?

Only where no row is an expected answer. If the row should always exist, .maybeSingle() turns the error into data null, and a missing policy goes unnoticed. Keep .single() there and fix the filter or the policy.