---
title: "Supabase RLS policy: príklady otestované cez API"
description: "Hotové Supabase RLS policy pre vlastné riadky, verejné profily, tímy a MFA. Každá vyskúšaná ako hosť, vlastník a iný používateľ, s výsledkami."
url: "https://madsalt.tech/sk/blog/supabase-rls-policy-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/sk/about/"
language: "sk"
published: 2026-10-27
---

# Supabase RLS policy: príklady otestované cez API

TL;DR

Nižšie sú politiky RLS, ktoré potrebuje väčšina aplikácií na Supabase. Každú som poslal cez REST API ako hosť, ako vlastník riadku a ako iný prihlásený používateľ. Tri výsledky stoja za to poznať skôr, než niečo skopírujete: update cudzieho riadku vráti `200` s prázdnym zoznamom, nie chybu; nevinne vyzerajúca insert politika na tabuľke členstiev dovolila používateľovi vstúpiť do inej firmy a čítať jej projekty; a zápis `(select auth.uid())` namiesto `auth.uid()` zrýchlil jeden dopyt zo 156 ms na 7,4 ms na 200 000 riadkoch.

Som Denys, full-stack a AI inžinier, a väčšina aplikácií, ktoré stavám alebo opravujem, beží na Supabase. Ukážok, ako napísať Supabase RLS policy, je všade dosť; čo urobia, keď na ne dopadne skutočná požiadavka, sa ukazuje zriedka. Tak som ich spustil.

**Ako som testoval.** Väčšina požiadaviek išla cez lokálny Supabase stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14, Auth 2.192.0) s publishable key a dvoma skutočnými prihlásenými používateľmi, A a B. Prípady, ktoré nepotrebujú HTTP, napríklad syntaktické chyby a časovanie, bežali na čistom lokálnom Postgrese 17.10 s rolami `anon` a `authenticated` zo Supabase a znovu vytvorenou funkciou `auth.uid()`. Všetko 24. októbra 2026, s vymyslenými riadkami.

## Ešte pred politikou: má tabuľka grant?

Politika rozhoduje, ktoré riadky rola vidí. Grant rozhoduje, či sa rola k tabuľke vôbec dostane, a na aktuálnych projektoch Supabase už nové tabuľky granty Data API automaticky nedostávajú (od 30. októbra 2026 pri všetkých projektoch). Na mojom stacku odpovedala tabuľka so zapnutým RLS a bez grantu takto:

| Požiadavka                   | Status | Telo                                                                           |
| ---------------------------- | ------ | ------------------------------------------------------------------------------ |
| Hosť `GET /invoices`         | `401`  | `permission denied for table invoices`, s nápovedou `GRANT SELECT ... TO anon` |
| Používateľ A `GET /invoices` | `403`  | to isté, s nápovedou pre `authenticated`                                       |

Takže krok jeden pri každej tabuľke, ktorú aplikácia číta z prehliadača:

```sql
alter table public.todos enable row level security;
grant select, insert, update, delete on public.todos to authenticated;
```

Udeľte len príkazy, ktoré prehliadač potrebuje, a len role `authenticated`, ak tabuľku nemajú čítať aj hostia.

## Ako napísať Supabase RLS policy, aby používatelia videli len svoje riadky?

Jedna politika na príkaz, každá obmedzená na prihlásených používateľov:

```sql
create policy "read own" on public.todos
  for select to authenticated using ((select auth.uid()) = user_id);
create policy "add own" on public.todos
  for insert to authenticated with check ((select auth.uid()) = user_id);
create policy "edit own" on public.todos
  for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "delete own" on public.todos
  for delete to authenticated using ((select auth.uid()) = user_id);
```

A vlastní dva riadky, B jeden. Čo sa vrátilo:

| Požiadavka od | Čo                                  | Status | Telo                                         |
| ------------- | ----------------------------------- | ------ | -------------------------------------------- |
| Hosť          | čítať všetko                        | `401`  | permission denied (žiadny grant pre `anon`)  |
| A             | čítať všetko                        | `200`  | 2 riadky používateľa A                       |
| A             | vložiť riadok patriaci B            | `403`  | `new row violates row-level security policy` |
| A             | upraviť riadok používateľa B        | `200`  | `[]`                                         |
| A             | zmazať riadok používateľa B         | `200`  | `[]`                                         |
| A             | zmeniť vlastníka svojho riadku na B | `403`  | `new row violates row-level security policy` |

Dva riadky `200 []` sú tie, ktoré si zapamätajte. RLS update ani delete riadkov, ktoré nevidíte, neodmietne; odfiltruje ich a nula zmenených riadkov je úspech. Ak vaša aplikácia berie „žiadna chyba“ ako „uložené“, povie používateľovi, že úprava prebehla, hoci sa nestalo nič. Vyžiadajte si zmenené riadky (`.select()` v supabase-js, `Prefer: return=representation` cez HTTP) a overte, že sa nejaký vrátil.

## Prečo update politika potrebuje WITH CHECK?

`using` vyberá, ktorých existujúcich riadkov sa používateľ smie dotknúť; `with check` rozhoduje, ako môžu tie riadky vyzerať potom. Porovnal som tri verzie tej istej update politiky:

| Update politika                                    | A odovzdá svoj riadok používateľovi B              |
| -------------------------------------------------- | -------------------------------------------------- |
| `using (uid = user_id) with check (uid = user_id)` | `403`, zamietnuté                                  |
| `using (uid = user_id)`, bez `with check`          | zamietnuté (Postgres použije `using` ako kontrolu) |
| `using (uid = user_id) with check (true)`          | `200`, riadok teraz patrí používateľovi B          |

Vynechať `with check` je bezpečnejšie, než to vyzerá, pretože Postgres siahne po výraze z `using`. `with check (true)`, ktoré niektoré generátory píšu, aby „povolili updaty“, je presne to, čo používateľovi dovolí presunúť riadok do cudzieho účtu. Napíšte kontrolu celú.

## Ako urobiť profily verejné, ale upraviteľné len vlastníkom?

```sql
create policy "public read" on public.profiles
  for select to anon, authenticated using (true);
create policy "own edit" on public.profiles
  for update to authenticated
  using ((select auth.uid()) = id) with check ((select auth.uid()) = id);
```

Na behu na čistom Postgrese: hosť prečítal oba profily, update hosťa zmenil 0 riadkov a `update profiles set display_name = 'x'` od používateľa A bez filtra zmenil presne 1 riadok, A vlastný. `using (true)` je v poriadku pre select nad dátami, ktoré majú byť verejné. Na akejkoľvek inej tabuľke je to to isté ako žiadne RLS a politika napísaná bez klauzuly `to` platí aj pre hostí: môj hosť prečítal „súkromný“ riadok cez politiku `using (true)`, ktorá `to` nemala.

## Ako zdieľať riadky vnútri tímu?

Obvyklý tvar: tabuľka `memberships` a politika na dátach, ktorá v nej vyhľadá tímy používateľa.

```sql
create policy "members read" on public.projects
  for select to authenticated
  using (org_id in (select org_id from public.memberships
                    where user_id = (select auth.uid())));
```

Tá politika je v poriadku. Únik bol vedľa nej. Tabuľke `memberships` som dal politiku, ktorú odporúča rad tutoriálov, „používatelia môžu vložiť svoje vlastné členstvo“:

```sql
create policy "join any org" on public.memberships
  for insert to authenticated with check (user_id = (select auth.uid()));
```

Kontroluje, komu riadok patrí, nie do ktorého tímu. A, člen firmy 1, poslal jednu požiadavku:

![Jedna insert politika na memberships. Používateľ A firmy 1 cez REST API. Politika: user_id = auth.uid(): Používateľ A → vstup do firmy 2 → 201 Created → číta mzdy firmy B; Bez insert politiky: Používateľ A → vstup do firmy 2 → zamietnuté → len vlastné projekty.](https://madsalt.tech/charts/sk/supabase-rls-policy-examples-join.svg)

*Obrázok 1. Môj test na lokálnom stacku Supabase (CLI 2.109.1), 24. októbra 2026.*

| Požiadavka používateľa A                  | Status | Telo                     |
| ----------------------------------------- | ------ | ------------------------ |
| čítať projekty                            | `200`  | `A roadmap`              |
| vložiť členstvo `{user_id: A, org_id: 2}` | `201`  | vytvorené                |
| znovu čítať projekty                      | `200`  | `A roadmap`, `B payroll` |

Oprava spočíva v tom, že `memberships` nedostane žiadnu insert politiku, takže do nej prehliadač nemôže zapisovať (na behu na Postgrese bol rovnaký insert potom zamietnutý s `42501`), a členov sa pridáva cez pozvánkový tok, ktorý beží na serveri alebo vo funkcii kontrolujúcej pozvánku. Používateľ patriaci do dvoch firiem je ďalšia pasca; napísal som ju v článku [Supabase multi-tenant RLS: únik dát u používateľa v 2 org](https://madsalt.tech/sk/notes/supabase-rls-user-in-two-organizations/).

## Čo ak politika potrebuje čítať vlastnú tabuľku?

„Členovia vidia svojich kolegov z tímu“ znamená politiku na `memberships`, ktorá číta `memberships`. Postgres ju pri dopyte odmietne:

```text
ERROR 42P17: infinite recursion detected in policy for relation "members2"
```

Presuňte vyhľadávanie do funkcie, ktorá beží ako jej vlastník, v schéme, ktorú API nevystavuje, s pevným `search_path`:

```sql
create schema private;
create function private.user_org_ids() returns setof int
  language sql stable security definer set search_path = ''
  as $$ select org_id from public.members2 where user_id = (select auth.uid()) $$;
revoke all on function private.user_org_ids() from public;
grant usage on schema private to authenticated;
grant execute on function private.user_org_ids() to authenticated;

create policy "see teammates" on public.members2
  for select to authenticated using (org_id in (select private.user_org_ids()));
```

Potom A videl oboch členov firmy 1 a B videl len seba. Funkciu nechajte mimo `public`: čokoľvek tam je, dá sa volať cez API.

## Ako vyžadovať MFA pri citlivých riadkoch?

Pridajte k bežnej politike reštriktívnu politiku. Reštriktívne politiky sa spájajú cez `and`, takže musia prejsť obe:

```sql
create policy "mfa only" on public.payouts as restrictive
  for select to authenticated using ((select auth.jwt() ->> 'aal') = 'aal2');
```

S rovnakým používateľom prečítala session na úrovni `aal1` 0 výplat a session na úrovni `aal2` 1.

## Ktoré tabuľky majú mať RLS zapnuté a žiadnu politiku?

Tabuľky, ktorých sa dotýka len váš server. RLS zapnuté bez politiky odoprie prehliadačovým rolám všetko: na behu na Postgrese prečítali hosť aj A 0 riadkov a insert od používateľa A bol zamietnutý s `42501`. Nechať RLS vypnuté je opak. Tabuľka s vypnutým RLS a predvolenými grantmi, čítaná s publishable key a bez prihlásenia:

| Požiadavka hosťa        | Status | Telo                |
| ----------------------- | ------ | ------------------- |
| `GET /leads`            | `200`  | oba riadky          |
| `DELETE /leads?id=gt.0` | `200`  | oba riadky, zmazané |

O tom je varovanie Advisora „RLS disabled in public“.

## Môže jedna politika pokryť viac príkazov?

Nie zoznamom. `for update, delete` je syntaktická chyba (`42601: syntax error at or near ","`), hoci sa stále objavuje vo zverejnených zbierkach ukážok. `for all` je platné a použije jedno `using` a jedno `with check` na každý príkaz, čo málokedy chcete. Insert politika s `using` tiež zlyhá: `only WITH CHECK expression allowed for INSERT`.

## Spomaľujú politiky dopyty?

Môžu. Výraz politiky sa vyhodnocuje pre každý riadok, ktorý Postgres zvažuje. Zmeral som `select * from big` ako používateľ A na 200 000 riadkoch patriacich 1 000 používateľom, z ktorých A vlastnil 200 (medián zo 7 behov, Postgres 17.10):

![Jedna politika len pre vlastníka, štyri zápisy. Milisekundy na prečítanie 200 vlastných riadkov z 200 000. Medián zo 7 behov. auth.uid() = user_id, bez indexu: 156,30; (select auth.uid()), bez indexu: 7,40; auth.uid() = user_id, s indexom: 0,21; (select auth.uid()), s indexom: 0,05.](https://madsalt.tech/charts/sk/supabase-rls-policy-examples.svg)

*Obrázok 2. Môj test na lokálnom Postgrese 17.10 s vytvorenými rolami Supabase, 24. októbra 2026.*

| Politika a index                            | Medián času |
| ------------------------------------------- | ----------- |
| `auth.uid() = user_id`, bez indexu          | 156,3 ms    |
| `(select auth.uid()) = user_id`, bez indexu | 7,4 ms      |
| `auth.uid() = user_id`, index na `user_id`  | 0,21 ms     |
| `(select auth.uid()) = user_id`, index      | 0,05 ms     |

Obalenie volania do `select` nechá Postgres vypočítať hodnotu raz na dopyt namiesto raz na riadok; zvyšok urobí index. [Sprievodca výkonom RLS](https://supabase.com/docs/guides/troubleshooting/rls-performance-and-best-practices-Z5Jjwv) od Supabase uvádza rovnaký rád zmeny na vlastnej tabuľke so 100 000 riadkami (179 ms na 9 ms). Rad blogových článkov tie čísla cituje ako vlastné; tie vyššie sú z môjho behu.

## Odkiaľ majú pochádzať role?

Z `app_metadata` alebo z vlastnej tabuľky, nikdy z `user_metadata`. Prihlásený používateľ si svoje `user_metadata` môže zmeniť cez `supabase.auth.updateUser()`, takže politika, ktorá kontroluje `auth.jwt() -> 'user_metadata' ->> 'role'`, mu dovolí prideliť si rolu sám. To isté urobí registračný trigger, ktorý kopíruje rolu z metadát registrácie; ten prípad je v článku [Supabase raw_user_meta_data: registrácia sa stala Ownerom](https://madsalt.tech/sk/notes/supabase-raw-user-meta-data-role/).

## Ako skontrolovať vlastné politiky?

- Bezplatná [Kontrola zabezpečenia Supabase](https://madsalt.tech/sk/tools/supabase-security-check/) číta váš projekt len na čítanie a označí politiky, ktoré platia vždy, zápisové politiky pre hostí, politiky dôverujúce metadátam používateľa a tabuľky s vypnutým RLS, ku každému s opravným SQL.
- Žiadny skener nevie, ktoré riadky komu patria. Na to posielajte požiadavky ako dvaja používatelia, tak ako v tomto článku; celá metóda pre aplikáciu s rolami je v článku [Ako testovať Supabase RLS: 30 z 87 oprávnení nerobilo nič](https://madsalt.tech/sk/blog/how-to-test-supabase-rls/).
- Čo Supabase zabezpečuje za vás a čo necháva na vás, je v článku [Je Supabase bezpečný? Čo pokrýva a čo musíte vy](https://madsalt.tech/sk/blog/is-supabase-secure/).
- Rovnaké testy vlastníka a tímu, spustené aj na Appwrite, sú v článku [Appwrite vs Supabase: bezpečnosť, rovnaké útoky na oba](https://madsalt.tech/sk/blog/appwrite-vs-supabase/).

**Limity.** Jeden lokálny stack a jeden notebook, vymyslené dáta, 24. októbra 2026. Časovanie závisí od hardvéru a tvaru tabuľky; dôležitejšie než milisekundy sú pomery. Politiky pre Storage a Realtime tu nie sú pokryté.

Nie ste si istí, že vaše politiky držia?

Rád sa s vami pozriem na schému. Bez ponuky.

[Ozvite sa](https://madsalt.tech/sk/#contact)
