Depuis 2026, creer une table dans public sur Supabase ne suffit plus pour la rendre accessible via supabase-js, PostgREST ou GraphQL. Si votre table existe mais que l'API repond avec une erreur de permission, le probleme vient souvent d'un GRANT manquant, pas de la RLS.
C'est un changement important. Supabase a introduit cette nouvelle logique le 28 avril 2026, elle devient le comportement par defaut pour les nouveaux projets le 30 mai 2026, puis elle sera appliquee aux projets existants le 30 octobre 2026. Si vous utilisez la Data API, il faut donc integrer les GRANT explicites dans vos migrations.
Le symptome le plus courant
Vous creez une table, vous lisez ou ecrivez avec supabase-js, et vous obtenez une reponse du type :
42501: permission denied for table your_table
Dans beaucoup de cas, PostgREST vous donne meme l'indice utile dans la reponse : le GRANT exact a ajouter.
Le point important a comprendre est simple :
- la table existe bien dans Postgres
- la Data API la voit comme un objet protege
- aucun role API n'a encore le droit d'y acceder
Autrement dit, votre schema n'est pas casse. Il n'est juste plus expose par defaut.
Qui est vraiment concerne
Vous etes concerne si votre application utilise l'une de ces voies :
supabase-js- un client Supabase cote navigateur
- un appel direct a
/rest/v1/ - un appel direct a
/graphql/v1/
Vous etes beaucoup moins concerne si votre application parle seulement a Postgres avec une connexion directe, par exemple via un ORM ou un serveur applicatif qui n'utilise pas la Data API.
C'est pour cela que beaucoup d'equipes se font surprendre : la base fonctionne, la migration passe, mais l'API auto-generee ne voit pas la table comme accessible tant que les privileges n'ont pas ete poses.
Le bon reflexe: mettre les GRANT dans la migration de creation
Le bon correctif n'est pas de cliquer un peu partout dans le dashboard. Le bon correctif est de traiter le sujet dans le flux normal du schema : la migration SQL.
Voici un exemple propre pour une table public.messages exposee a une application avec utilisateurs connectes :
grant usage on schema public to anon, authenticated, service_role;
grant select on table public.messages to anon; grant select, insert, update on table public.messages to authenticated; grant select, insert, update, delete on table public.messages to service_role;
alter table public.messages enable row level security;
create policy "users can read their own messages" on public.messages for select to authenticated using (auth.uid() = user_id);
create policy "users can create their own messages" on public.messages for insert to authenticated with check (auth.uid() = user_id); ```
La logique est volontairement separee :
GRANTdecide quels roles peuvent atteindre la table- la RLS decide quelles lignes ces roles peuvent lire ou modifier
Si vous oubliez le GRANT, la politique RLS ne sert a rien, car le role n'arrive meme pas jusqu'a la table.
Ce qu'il ne faut pas faire
Le plus mauvais reflexe consiste a reouvrir trop large juste pour faire disparaitre l'erreur.
Par exemple, donner ALL PRIVILEGES a anon sur toutes les tables public revient a annuler le durcissement que Supabase est justement en train d'imposer. C'est rapide, mais ce n'est pas propre, et c'est un tres mauvais signal pour la suite du projet.
Autre erreur frequente : croire que la RLS remplace les GRANT. Ce n'est pas le cas. Les deux couches travaillent ensemble, pas l'une a la place de l'autre.
Une regle simple par type de role
Dans la plupart des projets web classiques, vous pouvez partir de cette base :
service_role: acces complet, uniquement pour du code serveur de confianceauthenticated: acces limite aux operations utiles a l'app, puis restreint par la RLSanon: seulement si du contenu public doit etre lu sans connexion
Cette regle evite deja beaucoup de degats.
Si votre table contient des donnees internes, gardez anon en dehors. Si votre site affiche du contenu public, donnez seulement select a anon, pas plus.
Comment verifier que le correctif est bon
Avant de considerer le sujet comme termine, verifiez ces 5 points :
- La table a bien les
GRANTexplicites pour les roles utiles. - La table a la RLS activee si elle est exposee via la Data API.
- Les policies couvrent les cas de lecture et d'ecriture attendus.
- Le code client n'utilise jamais la cle
service_role. - La lecture ou l'ecriture reelle fonctionne avec le bon role, pas seulement avec votre compte admin.
Si vous voulez aller au bout de la logique, ajoutez aussi cette regle d'equipe : aucune nouvelle table public n'entre dans le projet sans GRANT explicites dans la migration.
Le cas des projets existants
Si votre projet existe deja, les anciennes tables peuvent continuer a fonctionner grace aux privileges historiques. C'est pratique a court terme, mais trompeur. Cela donne l'impression que tout est propre alors qu'en realite les nouvelles tables se comporteront differemment.
Le plus sain est de faire un passage de nettoyage :
- lister les tables exposees
- verifier quels roles ont acces a quoi
- remettre des
GRANTexplicites table par table - ne plus compter sur les privileges automatiques pour l'avenir
Ce qu'il faut retenir
Si une nouvelle table public ne repond plus via Supabase, le probleme n'est pas toujours dans votre code. Depuis 2026, il faut supposer d'abord qu'il manque un GRANT explicite.
La bonne methode est simple : creer la table, poser les GRANT, activer la RLS, puis ajouter les policies dans la meme migration. C'est plus clair, plus stable, et bien meilleur pour la securite du projet.
