---
title: "Como ativar o RLS no Supabase e o que devolve"
description: "Como ativar o RLS no Supabase pelo painel ou com SQL, testado em local: o que recebem a chave pública, dois utilizadores e a chave secreta em cada passo."
url: "https://madsalt.tech/pt/blog/how-to-enable-rls-in-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/pt/about/"
language: "pt"
published: 2026-11-04
---

# Como ativar o RLS no Supabase e o que devolve

Em resumo

Ative o RLS no Supabase com uma instrução por tabela, `alter table public.todos enable row level security;`, ou com a opção “Enable RLS” do painel. As tabelas criadas no editor SQL ou por uma migração não o trazem ativado por defeito. Logo depois de o ativar, a tabela fica calada em vez de dar erros. No meu teste local, a chave pública e dois utilizadores com sessão iniciada receberam `200 []` em select, update e delete, e só o insert falhou com `42501`. A chave secreta continuava a ver as 3 linhas. Uma vista simples sobre a tabela continuava a entregar as 3 linhas à chave pública. Desde 30 de outubro de 2026, uma tabela nova precisa ainda de um grant para que algo lhe chegue, por isso ative o RLS e escreva as políticas na mesma migração que o grant.

Mantenho um [verificador de segurança do Supabase](https://madsalt.tech/pt/tools/supabase-security-check/) gratuito, e “RLS disabled” é o achado que vejo pior explicado. A maioria dos guias diz que, depois de ativar o RLS, “a tabela fica inacessível”. Isso é verdade para as leituras e enganador para as escritas. Por isso construí uma tabela numa stack local do Supabase e enviei-lhe todo o tipo de pedidos, com a chave pública, com dois utilizadores com sessão iniciada e com a chave secreta, depois de cada passo abaixo.

## O que é o RLS no Supabase?

O RLS (row-level security, segurança ao nível da linha) é uma funcionalidade do Postgres que filtra linhas por pedido. O Supabase converte cada pedido do `supabase-js` numa consulta à base de dados que corre como um papel: `anon` para a chave pública (anon, publishable) e `authenticated` para um utilizador com sessão iniciada. Com o RLS ativado, o Postgres verifica as suas políticas linha a linha e devolve, altera ou apaga apenas as linhas que uma política permite. Com o RLS desativado, qualquer papel com um grant sobre a tabela chega a todas as linhas.

A chave secreta (service role) é diferente. O seu papel tem `BYPASSRLS`, por isso o RLS nunca se lhe aplica. É por isso que só deve estar num servidor. A diferença entre as duas chaves está em [Supabase anon key vs service role key: o que cada uma abre](https://madsalt.tech/pt/blog/supabase-anon-key-vs-service-role-key/).

## Como se ativa o RLS no Supabase?

De três maneiras, conforme a forma como a tabela foi criada:

- **Ao criar uma tabela no Table Editor**, deixe marcada a opção “Enable Row Level Security (RLS)”. No [código-fonte do painel](https://github.com/supabase/supabase/commit/33bd798d142183e162c25debcc50c8c711e68f66) do Supabase aparece como “Recommended”, e ao desmarcá-la surge um aviso de que a tabela será legível e alterável por qualquer pessoa.
- **Para uma tabela existente**, o [guia de segurança da API](https://supabase.com/docs/guides/api/securing-your-api) do Supabase indica: vá a Database, depois a Policies, e selecione “Enable RLS” na tabela.
- **Em SQL**, no editor SQL ou numa migração:

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

O guia do Supabase é explícito: as tabelas criadas no painel trazem o RLS ativado por defeito, e as criadas no editor SQL ou por outra ferramenta não. Se constrói o seu esquema com migrações ou com uma ferramenta de IA, verifique cada tabela.

## O que acontece logo depois de ativar o RLS?

Nada dá erro, exceto o insert. Criei `public.todos` com uma coluna de proprietário (`user_id uuid default auth.uid()`), concedi-a aos papéis da API como nos projetos mais antigos e adicionei três linhas: duas do utilizador A e uma do utilizador B. Depois ativei o RLS sem nenhuma política:

| Pedido                  | Chave pública   | Utilizador A    | Utilizador B    | Chave secreta       |
| ----------------------- | --------------- | --------------- | --------------- | ------------------- |
| Select                  | 200, 0 linhas   | 200, 0 linhas   | 200, 0 linhas   | 200, 3 linhas       |
| Insert de linha própria | 401, erro 42501 | 403, erro 42501 | 403, erro 42501 | 201, linha inserida |
| Update de uma linha     | 200, 0 linhas   | 200, 0 linhas   | 200, 0 linhas   | 200, 1 linha        |
| Delete de uma linha     | 200, 0 linhas   | 200, 0 linhas   | 200, 0 linhas   | 200, 1 linha        |

O erro do insert diz `new row violates row-level security policy`. Update e delete respondem `200` com um array vazio, por isso uma app que não verifica as linhas devolvidas informa “guardado” quando nada foi guardado. Leia as suas escritas depois de ativar o RLS, ou peça uma contagem com `Prefer: count=exact`.

Antes do RLS, com os mesmos grants, a chave pública podia ler as 3 linhas, inserir, e alterar ou apagar qualquer linha. O utilizador A podia editar e apagar a linha do utilizador B e passar-lhe as suas próprias linhas.

## Como voltar a tornar a tabela utilizável?

Escreva uma política por operação. O conjunto habitual para linhas próprias:

```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);
```

Com elas, o utilizador A via as suas 2 linhas e o utilizador B a sua 1. A chave pública continuava a ver 0. Quando A tentou inserir uma linha de B, ou passar uma linha sua para B, a resposta foi `403, 42501`. Essa recusa vem do `with check` no update. Quando A tentou alterar ou apagar a linha de B, a resposta foi `200, 0 linhas`. Há mais padrões (perfis públicos, equipas, MFA) em [Exemplos de RLS policy no Supabase, testados pela API](https://madsalt.tech/pt/blog/supabase-rls-policy-examples/).

Um pormenor da chave secreta: uma linha inserida por ela ficou com `user_id = null`, porque `auth.uid()` não tem nenhum utilizador por trás dessa chave. O RLS foi ignorado, mas o valor por defeito não fez o que o nome da coluna sugere.

## O que leu a chave pública em cada passo?

![Linhas lidas pela chave pública, de 3. Uma tabela do Supabase com 3 linhas, após cada passo. Tabela nova, sem grant, RLS off (erro 42501): 0; Com grant, RLS off: 3; RLS on, sem política: 0; RLS on, políticas de linhas próprias: 0; Vista simples sobre a tabela: 3; Vista com security_invoker: 0.](https://madsalt.tech/charts/pt/how-to-enable-rls-in-supabase.svg)

*Figura 1. Stack local do Supabase (CLI 2.109.1, Postgres 17.6), 28 de outubro de 2026.*

Dois passos deixam passar tudo: um grant sem RLS, e uma vista simples sobre uma tabela com políticas corretas. As duas secções seguintes explicam ambos.

## Ainda é preciso RLS depois da mudança de grants de 30 de outubro?

Sim. Desde 30 de outubro de 2026, o Supabase deixou de dar grants automáticos aos papéis da API nas tabelas novas de `public`, tanto em projetos novos como em existentes. A minha stack local já se comporta assim. Uma tabela acabada de criar com o RLS desativado respondeu `42501` a todos os papéis, incluindo a chave secreta, por isso nada vazou. Os pormenores estão em [Supabase, 30 de outubro: o que falha com os novos grants](https://madsalt.tech/pt/blog/supabase-october-30-grant-change/).

Mas o grant volta assim que a app precisa da tabela, e a partir daí o RLS é a única coisa entre a chave pública e todas as linhas. Por isso ponha tudo numa só migração, por esta ordem:

```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;
```

Conceda apenas aos papéis que precisam da tabela. Quando revoguei tudo a `anon`, o select da chave pública passou de `200 []` a `401, 42501`. O grant é o primeiro cadeado; o RLS, o segundo.

## Porque é que uma vista continua a deixar vazar linhas?

Porque uma vista corre com os direitos do seu proprietário, e no Supabase esse é o `postgres`, que ignora o RLS. Com as políticas de linhas próprias já em `todos`, um simples `create view public.todos_view as select * from public.todos` devolveu as 3 linhas à chave pública e ao utilizador B.

Recriada com `with (security_invoker = true)`, a mesma vista devolveu 0 linhas à chave pública, 2 a A e 1 a B, exatamente como a tabela:

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

O Advisor do Supabase assinala a variante simples como `security_definer_view`; o que significa e como corrigir está em [a nota sobre security definer view no Supabase](https://madsalt.tech/pt/notes/supabase-security-definer-view/).

## Como ativar o RLS automaticamente em cada tabela nova?

Com um event trigger. O [guia de event triggers](https://supabase.com/docs/guides/database/postgres/event-triggers) do Supabase fornece uma função `rls_auto_enable()` e um trigger `ensure_rls` que corre depois de `create table`, `create table as` e `select into` em `public`. Instalei-o tal como está publicado e obtive:

- Uma tabela criada depois com `create table` tinha o RLS ativado sem nenhum `alter`. A chave pública recebeu `200 []` no select e `401` no insert.
- Uma tabela criada com `create table as select` também tinha o RLS ativado.
- Uma tabela criada antes do trigger continuou desativada. O trigger não é retroativo.

Sem o trigger, uma segunda tabela criada ao lado da protegida tinha o RLS desativado, e depois de concedida, a chave pública lia-a e escrevia-lhe livremente. Cobre também apenas `public`; os outros esquemas expostos precisam da sua própria linha na função.

## O FORCE ROW LEVEL SECURITY ajuda?

Não para os papéis com que se preocuparia. `alter table ... force row level security` faz o RLS aplicar-se também ao proprietário da tabela. No Supabase, tanto o `postgres` como o `service_role` têm `BYPASSRLS`, por isso depois de `force` o proprietário continuava a contar 3 linhas, a chave secreta continuava a ver tudo, e uma vista propriedade do `postgres` continuava a vazar. Só mudou alguma coisa para um papel proprietário que criei sem `BYPASSRLS`: 2 linhas antes, 0 depois.

## Como verificar o seu próprio projeto?

- No editor SQL, liste as tabelas de `public` com o RLS desativado:

```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;
```

- O Advisor do painel reporta o mesmo como `rls_disabled_in_public`; o que esse erro significa está em [a nota sobre RLS disabled in public](https://madsalt.tech/pt/notes/supabase-rls-disabled-in-public/).
- O [verificador de segurança do Supabase](https://madsalt.tech/pt/tools/supabase-security-check/) gratuito lê o seu projeto em modo só de leitura e lista as tabelas com o RLS desativado a que a API chega, as vistas que contornam o RLS e as políticas que permitem o acesso a todos, com o SQL de correção para cada uma.
- Um scanner não pode saber que linhas pertencem a quem. Para o provar, envie pedidos como dois utilizadores, como acima. O método completo para uma app com papéis está em [Testar Supabase RLS](https://madsalt.tech/pt/blog/how-to-test-supabase-rls/).

## Como testei

- Supabase CLI 2.109.1 num portátil: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Dois utilizadores com sessão iniciada por palavra-passe, mais as chaves locais publishable e secreta.
- Uma tabela, `public.todos`, reposta nas mesmas 3 linhas antes de cada pedido. Cada passo foi aplicado como SQL, e cada pedido passou pela API REST, registando-se o código de estado e o corpo.
- Os grants dos passos 2 a 5 foram adicionados à mão, para igualar um projeto criado antes de 30 de outubro de 2026.

**Limites.** Uma stack local, dados inventados, 28 de outubro de 2026. Os passos do painel são citados da documentação do Supabase, não percorridos com cliques. As tabelas criadas pelo papel próprio do painel podem receber outros grants por defeito. Não testei isso, nem Storage, Realtime ou GraphQL.

Não tem a certeza de que as suas tabelas estão fechadas?

Falo com gosto sobre isso e vejo um esquema consigo. Sem pitch.

[Fale comigo](https://madsalt.tech/pt/#contact)
