---
title: "Sådan aktiverer du RLS i Supabase: hvad du får tilbage"
description: "Sådan aktiverer du Supabase RLS i dashboardet eller med SQL, testet lokalt, med hvad den offentlige nøgle, to brugere og den hemmelige nøgle får."
url: "https://madsalt.tech/da/blog/how-to-enable-rls-in-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/da/about/"
language: "da"
published: 2026-11-04
---

# Sådan aktiverer du RLS i Supabase: hvad du får tilbage

TL;DR

Aktivér RLS i Supabase med én sætning pr. tabel, `alter table public.todos enable row level security;`, eller med valget "Enable RLS" i dashboardet. Tabeller oprettet i SQL-editoren eller af en migration får det ikke som standard. Lige efter du har aktiveret det, bliver tabellen stille frem for højlydt. På min lokale test fik den offentlige nøgle og to loggede brugere `200 []` på select, update og delete, og kun insert fejlede med `42501`. Den hemmelige nøgle så stadig alle 3 rækker. En almindelig view over tabellen gav stadig alle 3 rækker til den offentlige nøgle. Siden den 30. oktober 2026 kræver en ny tabel også et grant, før noget når den, så aktivér RLS og skriv policies i samme migration som granten.

Jeg driver en gratis [Supabase security checker](https://madsalt.tech/da/tools/supabase-security-check/), og "RLS disabled" er det fund, jeg ser forklaret dårligst. De fleste guides siger, at efter du har aktiveret RLS, "bliver tabellen utilgængelig". Det er sandt for læsning og vildledende for skrivning. Så jeg byggede én tabel på en lokal Supabase-stack og sendte alle slags forespørgsler til den, som den offentlige nøgle, som to loggede brugere og som den hemmelige nøgle, efter hvert trin nedenfor.

## Hvad er RLS i Supabase?

RLS (row-level security) er en Postgres-funktion, der filtrerer rækker pr. forespørgsel. Supabase gør hver forespørgsel fra `supabase-js` til en databaseforespørgsel, der kører som en rolle: `anon` for den offentlige (anon, publishable) nøgle, `authenticated` for en logget bruger. Med RLS slået til tjekker Postgres dine policies for hver række og returnerer, ændrer eller sletter kun de rækker, en policy tillader. Med RLS slået fra når enhver rolle med et grant på tabellen alle rækker.

Den hemmelige nøgle (service role) er anderledes. Dens rolle har `BYPASSRLS`, så RLS gælder aldrig for den. Derfor hører den kun hjemme på en server. Forskellen på de to nøgler står i [Supabase anon key vs service role key: hvad åbner de?](https://madsalt.tech/da/blog/supabase-anon-key-vs-service-role-key/).

## Hvordan aktiverer du RLS i Supabase?

Tre måder, afhængigt af hvordan tabellen blev lavet:

- **Når du opretter en tabel i Table Editor**, så lad "Enable Row Level Security (RLS)" være afkrydset. I Supabases [dashboard-kode](https://github.com/supabase/supabase/commit/33bd798d142183e162c25debcc50c8c711e68f66) er det markeret "Recommended", og slår du det fra, vises en advarsel om, at tabellen bliver offentligt læsbar og skrivbar.
- **For en eksisterende tabel** siger Supabases [guide til sikring af API'et](https://supabase.com/docs/guides/api/securing-your-api): gå til Database, derefter Policies, og vælg "Enable RLS" på tabellen.
- **I SQL**, i SQL-editoren eller en migration:

```sql
alter table public.todos enable row level security;
```

Supabases guide er tydelig på, at tabeller oprettet i dashboardet har RLS slået til som standard, og at tabeller oprettet i SQL-editoren eller med et andet værktøj ikke har det. Bygger du dit skema med migrationer eller et AI-værktøj, så tjek hver tabel.

## Hvad sker der lige efter, du aktiverer RLS?

Intet giver fejl undtagen insert. Jeg oprettede `public.todos` med en ejerkolonne (`user_id uuid default auth.uid()`), gav den grants til API-rollerne på samme måde som ældre projekter og tilføjede tre rækker: to ejet af bruger A og én af bruger B. Derefter aktiverede jeg RLS uden policy:

| Forespørgsel      | Offentlig nøgle | Bruger A        | Bruger B        | Hemmelig nøgle    |
| ----------------- | --------------- | --------------- | --------------- | ----------------- |
| Select            | 200, 0 rækker   | 200, 0 rækker   | 200, 0 rækker   | 200, 3 rækker     |
| Insert egen række | 401, fejl 42501 | 403, fejl 42501 | 403, fejl 42501 | 201, række indsat |
| Update en række   | 200, 0 rækker   | 200, 0 rækker   | 200, 0 rækker   | 200, 1 række      |
| Delete en række   | 200, 0 rækker   | 200, 0 rækker   | 200, 0 rækker   | 200, 1 række      |

Insert-fejlen lyder `new row violates row-level security policy`. Update og delete svarer `200` med en tom liste, så en app, der ikke tjekker de returnerede rækker, melder "gemt", når intet blev gemt. Læs dine skrivninger tilbage, efter du har aktiveret RLS, eller bed om et antal med `Prefer: count=exact`.

Før RLS, med de samme grants, kunne den offentlige nøgle læse alle 3 rækker, indsætte og opdatere eller slette enhver række. Bruger A kunne redigere og slette bruger Bs række og give sine egne rækker til B.

## Hvordan gør du tabellen brugbar igen?

Skriv en policy pr. operation. Det sædvanlige sæt for egne rækker:

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

Med dem så bruger A sine 2 rækker og bruger B sin 1. Den offentlige nøgle så stadig 0. Da A forsøgte at indsætte en række ejet af B eller at flytte sin egen række til B, var svaret `403, 42501`. Den afvisning kommer fra `with check` på update. Da A forsøgte at opdatere eller slette Bs række, var svaret `200, 0 rækker`. Flere mønstre (offentlige profiler, teams, MFA) står i [Supabase RLS policy-eksempler, hver testet via API](https://madsalt.tech/da/blog/supabase-rls-policy-examples/).

En detalje fra den hemmelige nøgle: en række, den indsatte, fik `user_id = null`, fordi `auth.uid()` ikke har nogen bruger bag den nøgle. RLS blev sprunget over, men standardværdien gjorde ikke det, kolonnenavnet antyder.

## Hvad læste den offentlige nøgle efter hvert trin?

![Rækker den offentlige nøgle læste, af 3. Én Supabase-tabel med 3 rækker, efter hvert trin. Ny tabel, intet grant, RLS fra (fejl 42501): 0; Grant tilføjet, RLS fra: 3; RLS til, ingen policy: 0; RLS til, egne-rækker-policies: 0; Almindelig view over tabellen: 3; View med security_invoker: 0.](https://madsalt.tech/charts/da/how-to-enable-rls-in-supabase.svg)

*Figur 1. Lokal Supabase-stack (CLI 2.109.1, Postgres 17.6), 28. oktober 2026.*

To trin lækker alt: et grant uden RLS og en almindelig view over en tabel med korrekte policies. De næste to afsnit forklarer begge.

## Har du stadig brug for RLS efter grant-ændringen den 30. oktober?

Ja. Siden den 30. oktober 2026 giver Supabase ikke længere nye tabeller i `public` automatiske grants til API-rollerne, hverken på nye eller eksisterende projekter. Min lokale stack opfører sig allerede sådan. En nyoprettet tabel med RLS slået fra svarede `42501` til hver rolle, også den hemmelige nøgle, så intet lækkede. Detaljerne står i [Supabase 30. oktober: hvad knækker, og hvordan tjekker du](https://madsalt.tech/da/blog/supabase-october-30-grant-change/).

Men granten kommer tilbage, så snart appen skal bruge tabellen, og fra da af er RLS det eneste mellem den offentlige nøgle og hver række. Så læg det hele i én migration, i denne rækkefølge:

```sql
create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;
```

Giv kun grants til de roller, der har brug for tabellen. Da jeg tog alt fra `anon`, skiftede den offentlige nøgles select fra `200 []` til `401, 42501`. Granten er den første lås, RLS er den anden.

## Hvorfor lækker en view stadig rækker?

Fordi en view kører med sin ejers rettigheder, og i Supabase er det `postgres`, som omgår RLS. Med policies for egne rækker på plads på `todos` gav en almindelig `create view public.todos_view as select * from public.todos` alle 3 rækker til den offentlige nøgle og til bruger B.

Oprettet igen `with (security_invoker = true)` gav den samme view 0 rækker til den offentlige nøgle, 2 til A og 1 til B, præcis som tabellen:

```sql
create view public.todos_view with (security_invoker = true) as
  select * from public.todos;
```

Supabases Advisor markerer den almindelige slags som `security_definer_view`; hvad det betyder, og hvordan du retter det, står i [noten om Supabase security definer view](https://madsalt.tech/da/notes/supabase-security-definer-view/).

## Hvordan aktiverer du RLS automatisk på hver ny tabel?

Med en event trigger. Supabases [guide til event triggers](https://supabase.com/docs/guides/database/postgres/event-triggers) giver en funktion `rls_auto_enable()` og en trigger `ensure_rls`, der kører efter `create table`, `create table as` og `select into` i `public`. Jeg installerede den som udgivet og fik:

- En tabel oprettet bagefter med `create table` havde RLS slået til uden nogen `alter`. Den offentlige nøgle fik `200 []` på select og `401` på insert.
- En tabel oprettet med `create table as select` havde også RLS slået til.
- En tabel oprettet før triggeren forblev slået fra. Triggeren virker ikke bagudrettet.

Uden triggeren havde en anden tabel, oprettet ved siden af den beskyttede, RLS slået fra, og når den fik et grant, kunne den offentlige nøgle læse og skrive den frit. Den dækker heller kun `public`; andre eksponerede skemaer skal have deres egen linje i funktionen.

## Hjælper FORCE ROW LEVEL SECURITY?

Ikke for de roller, du bekymrer dig om. `alter table ... force row level security` får RLS til også at gælde for tabellens ejer. På Supabase har både `postgres` og `service_role` `BYPASSRLS`, så efter `force` talte ejeren stadig 3 rækker, den hemmelige nøgle så stadig alt, og en view ejet af `postgres` lækkede stadig. Det ændrede kun noget for en ejerrolle, jeg oprettede uden `BYPASSRLS`: 2 rækker før, 0 efter.

## Hvordan tjekker du dit eget projekt?

- Vis i SQL-editoren tabellerne i `public` med RLS slået fra:

```sql
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity
order by 1;
```

- Advisor i dashboardet rapporterer det samme som `rls_disabled_in_public`; hvad den fejl betyder, står i [noten om RLS disabled in public](https://madsalt.tech/da/notes/supabase-rls-disabled-in-public/).
- Det gratis [Supabase security check](https://madsalt.tech/da/tools/supabase-security-check/) læser dit projekt skrivebeskyttet og viser tabeller med RLS slået fra, som API'et kan nå, views der omgår RLS, og policies der tillader alle, med rette-SQL til hver.
- En scanner kan ikke vide, hvilke rækker der tilhører hvem. For at bevise det skal du sende forespørgsler som to brugere, som ovenfor. Den fulde metode for en app med roller står i [Test Supabase RLS: 30 af 87 rettigheder gjorde intet](https://madsalt.tech/da/blog/how-to-test-supabase-rls/).

## Sådan testede jeg

- Supabase CLI 2.109.1 på en bærbar: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. To brugere logget ind med adgangskode, plus de lokale publishable og secret keys.
- Én tabel, `public.todos`, nulstillet til de samme 3 rækker før hver forespørgsel. Hvert trin blev udført som SQL, og hver forespørgsel gik over REST-API'et, med statuskode og body noteret.
- Grants til trin 2 til 5 blev tilføjet i hånden, så de svarer til et projekt oprettet før den 30. oktober 2026.

**Begrænsninger.** Én lokal stack, opfundne data, 28. oktober 2026. Dashboard-trinnene er citeret fra Supabases dokumentation, ikke klikket igennem. Tabeller oprettet af dashboardets egen rolle kan få andre standardgrants. Det testede jeg ikke, og heller ikke Storage, Realtime eller GraphQL.

Usikker på, om dine tabeller er låst?

Jeg ser gerne på et skema sammen med dig. Uden salgstale.

[Kontakt mig](https://madsalt.tech/da/#contact)
