Cannot coerce the result to a single JSON object

A Supabase query asked for exactly one row with single(), but its filter matched no rows or more than one.

Supabase and browser console
Cannot coerce the result to a single JSON object

Also appears as

  • {"code":"PGRST116","details":"The result contains 0 rows","hint":null,"message":"Cannot coerce the result to a single JSON object"}
  • JSON object requested, multiple (or no) rows returned

Prompt to fix it

A Supabase query fails with "Cannot coerce the result to a single JSON object" (PGRST116). Find the query that ends in .single() and check the error’s details to see whether it got 0 rows or several. For 0 rows, check whether the row exists and whether a row-level security policy hides it from this user. If no row is a normal case, switch to .maybeSingle() and handle null in the UI. For several rows, filter on a unique column. Keep RLS on, and don’t add .limit(1) just to hide duplicates.

Paste into Layerwise or any AI coding tool.

What it means

Ending a supabase-js query with .single() tells Supabase’s API to return one object instead of a list. If the query matches anything other than exactly one row, the API refuses with code PGRST116 and status 406, and details gives the count, like The result contains 0 rows. Projects on older API versions, and .maybeSingle() when it finds several rows, word it “JSON object requested, multiple (or no) rows returned.”

Zero rows is the common case, and row-level security (RLS) is often why. A row hidden by RLS looks exactly like a row that doesn’t exist, so a profile that’s really there can still come back as 0 rows. An update(...).eq(...).select().single() behaves the same way: if no UPDATE policy matches, nothing changes and nothing comes back.

Use .maybeSingle() when “not found” is a normal answer, such as a profile that hasn’t been created yet. It returns null for zero rows. When several rows match, tighten the filter to a unique column such as id.

Common causes

  • The row doesn’t exist yet, such as the profile of someone who just signed up.
  • Row-level security hides the row from the current user, so the query sees 0 rows.
  • The filter isn’t unique, like .eq('email', email) on a table with repeated emails, so it matches several rows.
  • An update followed by .select().single() matched no rows, because of its filter or a missing UPDATE policy.

How to fix it

  1. Read details in the error to see whether the query found 0 rows or several.
  2. For 0 rows, check in the SQL editor that the row exists, then check the table’s SELECT policies (and UPDATE policies for updates) for the signed-in user.
  3. If “no row” is a valid result, switch to .maybeSingle() and handle null in the UI.
  4. For several rows, filter on a unique column such as id, or add a unique constraint so duplicates can’t be created.

Example fix

// Zero rows is a normal answer here: the profile may not exist yet.
const { data: profile, error } = await supabase
  .from('profiles')
  .select('*')
  .eq('id', user.id)
  .maybeSingle();

if (error) throw error;
if (!profile) {
  // Show onboarding, or create the profile.
}
All errors
new row violates row-level security policy for table "todos"

New row violates row-level security policy

Postgres refused to save a row because no row-level security policy on that table lets the current user write it.

TypeError: Cannot read properties of undefined (reading 'map')

Cannot read properties of undefined

The code asked for a property of a value that doesn’t exist yet, usually data that hasn’t loaded or a field the API didn’t return.

duplicate key value violates unique constraint "profiles_pkey"

Duplicate key value violates unique constraint

The app tried to save a row whose ID, email, or other unique value is already in the table, so Postgres rejected the copy.

relation "todos" does not exist

Relation does not exist

The query names a table that Postgres can’t find in this database, usually because it was never created here or has a different name.

Build with an agent that checks its work

The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.