# Access rules (RLS)

This is where things go wrong most often on this platform. You don't notice, because **you get an empty list back, not an error**.

## What you must know

A new table is created with access rules **turned on** and **no rules at all**.
With no rules, nobody can see the table. You get an empty array back, not an error.

```sql
create table notes (id uuid primary key default gen_random_uuid(), body text);
-- select with the publishable key now: []  (not an error)
```

## Role names

They differ per project. They are not a fixed `anon`/`authenticated`.
`list_policies` tells you the project's three real role names.

| Role | Who |
|---|---|
| `<ref>_anon` | People who are not signed in |
| `<ref>_authenticated` | People who are signed in |
| `<ref>_service_role` | The server. Bypasses the rules entirely |

**SQL written for Supabase runs as it is (Supabase name translation).** In run_sql, query_sql, apply_migrations, the console's SQL editor and `oneend db push`,
`public.`, `auth.` and `storage.` become `app_<ref>.`, `auth_<ref>.` and `stor_<ref>.`, and `anon`, `authenticated` and `service_role` after `to`/`from` become the project's roles.
The schema in a string that names an object (`'public.orders'::regclass`, `to_regclass('public.orders')`, `'public'::regnamespace`) is replaced too.
The answer's `translated` says what was replaced. Other strings and comments are left alone; a direct connection (psql) is not translated.
Everything runs with the search_path `app_<ref>, auth_<ref>, public, extensions`, so an unqualified `create table orders` or `'orders'::regclass` means this project's own.

## Three common rules

```sql
-- readable by anyone
create policy "public read" on notes for select to <ref>_anon using (true);

-- only the signed-in user's own rows
create policy "own rows" on notes for all to <ref>_authenticated
  using (user_id = auth_<ref>.uid());

-- signed-in users may write, but never in someone else's name
create policy "insert own" on notes for insert to <ref>_authenticated
  with check (user_id = auth_<ref>.uid());
```

`using` is checked on reads, `with check` on writes. Put only `using` on an insert and nobody can write.

## How to check

Look at the rules with `list_policies`, and use `read_rows` to see what the publishable key can actually see.
After creating a table or changing rules, run `get_advisors` to catch problems such as tables with RLS off or rules that let anyone write.

In SQL an app runs at every start (Figma Make's kv_store and the like), give an index a name and `if not exists`: `create index if not exists kv_store_x_key_idx on kv_store_x (key text_pattern_ops);`. An unnamed `create index on …` makes one more identical index each time it runs (`_idx1`, `_idx2` …), and every write updates all of them. `get_advisors` reports them as duplicate_index with the `drop index` statements that keep one.

If you made a rule and the table is still empty, the role name is the most likely culprit.

---

https://end.blackall.ai/docs/access-rules?lang=en
