Skip to main content
Troubleshooting8 min readPublished September 20, 2026Updated September 22, 2026

Why can't my frontend access my database?

There are three different failures wearing the same face: the request never reached the database, the database refused it, or the database allowed it and no rows matched the rules. An empty list with no error is almost always the third.

The problem

A list is empty, a detail page has no content, or a save appears to work and nothing is stored — yet you can see the rows in your database tooling.

What it means

Seeing rows in the dashboard proves nothing about your app, because the dashboard connects as an administrator and your app connects as a user. The difference between those two connections is where this bug lives.

Common causes

  • Row-level access rules are enabled with no policy allowing this role, so the result is empty.
  • A policy exists but compares the wrong column, or targets a role your app does not use.
  • Table permissions were never granted to the roles your app connects as, producing a permission error rather than emptiness.
  • The query filters on a value that is null or undefined at the moment it runs.
  • The client is pointed at a different project or environment than you are looking at.
  • The data is being fetched before the session exists, so the query runs as an anonymous visitor.

How to diagnose it

  1. 1Log the full response, not just the data — hosted clients return an error object that is easy to ignore.
  2. 2In the Network tab, read the response body of the database request.
  3. 3A permission error names the missing grant. An empty array with no error points at access rules or the filter.
  4. 4Print the filter values being used. A query filtered by an undefined user id matches nothing.
  5. 5Run the same query in your database tooling as the specific user, not as the administrator.
Do not discard the error
const { data, error } = await supabase.from("notes").select("*");
if (error) console.error("notes query failed", error);
console.log("rows", data?.length, "filter user", userId);

How to fix it

FindingFix
No policy for this roleAdd a policy that names the role and the ownership condition
Missing grantsGrant the required privileges to the roles your policies allow
Filter value undefinedWait for the session before querying, or guard the query
Wrong projectCorrect the URL and key for this environment
Query runs anonymouslyMove the fetch after authentication has resolved

Switching to an admin key to make it work is not a fix. It removes the rule that was doing its job and exposes every row.

What AI may have done

  • Created a table and enabled access rules without writing a policy for reading.
  • Written a policy for one role while the app connects as another.
  • Left out the grant statements that make a table reachable at all.
  • Fetched data in a component that renders before the user session is available.

How to verify the fix

  1. 1Reload the page as yourself and confirm your rows appear.
  2. 2Sign in as a second account and confirm you see that account's rows and none of yours.
  3. 3Sign out and confirm the page is empty or prompts to sign in, without errors.
  4. 4Try to read another user's record by id directly and confirm it is refused.

How to prevent it

  • Write the policy in the same change as the table, never later.
  • Always surface the error object in development.
  • Keep a second test account and use it whenever you touch data access.
  • Prefer owner-based policies over role checks where ownership is the real rule.

Frequently asked questions

Is it safe for a browser to talk to a database at all?
Yes, when access rules are enforced per row and the key used is the publishable one. The rules are what make it safe.
Why does it work for me and nobody else?
Because you own the data, so every ownership condition passes. Test with a second account.
Should I query from the server instead?
Sometimes — for joins, secrets or heavy work. It does not remove the need for access rules.

Practice this in MessyDev

Reading it once helps. Doing it once sticks. These are the hands-on parts of MessyDev that cover the same ground.

Keep going