Sicherheitsgrundlagen
Supabase "permission denied for schema public": drei Ursachen
Supabase "permission denied for schema public" heißt: Ein Grant auf das Schema fehlt. Was ihn entfernt hat und welche Zeile ihn zurückbringt.

Kurz gesagt
- Supabase "permission denied for schema public" bedeutet, dass die Rolle hinter der Anfrage das Schema public überhaupt nicht betreten darf. Postgres hat also abgelehnt, bevor es sich auch nur eine Tabelle angesehen hat.
- Jedes Supabase-Projekt startet mit diesem Recht für anon, authenticated und service_role. Bekommt deine App diesen Fehler, hat etwas es entfernt: Das Schema wurde gelöscht und neu angelegt, oder ein Skript hat den Grant entzogen.
- Zurückholen lässt er sich mit einer Zeile SQL. Der Grant lässt eine Rolle nur bis zu deinen Tabellen, welche Zeilen ein Besucher bekommt, entscheidet weiterhin Row Level Security. Prüf diese Regeln, bevor du es für erledigt hältst.
Gestern lief deine App noch. Heute bleibt jeder Bildschirm, der etwas aus
Supabase liest, leer oder zeigt einen Fehler, und in der Browserkonsole steht bei
jedem dieselbe Zeile: permission denied for schema public. Du öffnest das
Supabase-Dashboard, und alle Tabellen sind noch da. Der SQL Editor liest sie
ohne jede Beschwerde.
Die Antwort hinter dieser Zeile kommt meist als 401 oder 403. Auf die zwei Felder gekürzt, auf die es ankommt, sieht sie so aus:
{
"code": "42501",
"message": "permission denied for schema public"
}
Supabase "permission denied for schema public" kommt von Postgres, der
Datenbank, auf der Supabase läuft, und die Liste der Ursachen ist kurz. Jedes
Supabase-Projekt wird mit einem Grant angelegt, der deine App ins Schema
public lässt. Jemand oder etwas hat ihn entfernt, und zurückholen lässt er sich
mit einer Zeile SQL.
Hier ist der Teil, den man leicht verkehrt herum versteht: Dieser Grant ist die Eingangstür des Gebäudes, und die Schlösser für deine Daten liegen weiter drinnen. Wenn ihn jemand entfernt hat, um Fremde draußen zu halten, hat er deine App gleich mit ausgesperrt. Und ob es sicher ist, die Tür wieder zu öffnen, hängt an diesen inneren Schlössern. Sie sind es, was du prüfen solltest, bevor du die Sache für erledigt hältst.
Was bedeutet "permission denied for schema public"?
Es bedeutet, dass die Rolle hinter der Anfrage das Schema public nicht
betreten darf. Postgres hat also abgelehnt, bevor es sich auch nur eine Tabelle
angesehen hat.
Ein Schema ist ein benannter Ordner mit Tabellen in deiner Datenbank.
public ist der, in den Lovable, Bolt, v0, Cursor, Claude Code und Replit deine
Tabellen legen, solange ihnen niemand etwas anderes sagt. Eine Rolle ist die
Art von Besucher, von der eine Anfrage kommt: anon ist jemand, der nicht
angemeldet ist, authenticated jemand, der angemeldet ist, und service_role
dein eigener Servercode mit dem geheimen Schlüssel. Ein Grant gibt einer
Rolle ein Recht auf eine Sache.
Das Recht, um das es bei diesem Fehler geht, heißt USAGE, und die
Dokumentation von Postgres
beschreibt es als das Recht, Objekte innerhalb des Schemas nachzuschlagen
("look up"). Eine Rolle ohne dieses Recht erreicht keine Tabelle in diesem
Schema, ganz gleich, was in den Einstellungen der Tabelle selbst steht.
Wenn du dir durch
die Tabellen-Variante dieses Fehlers
angewöhnt hast, jede Tabelle als Raum mit einer Tür pro Rolle zu sehen, dann ist
das Schema das Gebäude, in dem diese Räume liegen, und USAGE ist seine
Eingangstür. permission denied for schema public heißt, dass deine Anfrage
draußen vor dieser Tür steht. Welche Räume ihre Türen offen haben, spielt keine
Rolle, denn bis zu einem Raum kommt die Anfrage nie.
Supabase gibt diese Eingangstür allen drei Rollen, wenn ein Projekt angelegt wird. Das eigene Datenbank-Image von Supabase führt diese Zeile in seinem ersten Setup-Skript aus:
grant usage on schema public to postgres, anon, authenticated, service_role;
Wenn deine App diesen Fehler bekommt, war der Grant also einmal da und ist jetzt weg.
Warum funktioniert der SQL Editor trotzdem?
Weil der SQL Editor keine der Rollen deiner App benutzt. Ein
Troubleshooting-Artikel von Supabase
sagt, dass eine Abfrage von dort "routed through the postgres role at the
database level" wird, also auf Datenbankebene über die Rolle postgres läuft.
postgres ist die Rolle, der deine Tabellen gehören, und wenn das Schema selbst
über das Dashboard oder von deinem Builder neu angelegt wurde, gehört ihr auch
das. Ein Eigentümer braucht keinen Grant, um sein eigenes Gebäude zu betreten.
Also zeigt das Dashboard jede Tabelle, ein select im SQL Editor liefert jede
Zeile, und dieselbe Anfrage aus deiner App wird an der Eingangstür abgewiesen.
Nichts auf diesen Bildschirmen verrät dir, dass die Rollen deiner App den Weg
hinein verloren haben.
Um zu sehen, was deine App sieht, frag Postgres direkt nach ihren Rollen. Füg das in den SQL Editor ein:
select rolname,
has_schema_privilege(rolname, 'public', 'USAGE') as can_enter
from pg_roles
where rolname in ('anon', 'authenticated', 'service_role');
Jede Zeile sollte true zeigen. Eine Zeile mit false ist eine Rolle, die deine
App benutzt und die nicht mehr hineinkommt.
Der Grant öffnet das Gebäude, die Schlösser liegen drinnen
USAGE lässt eine Rolle bis zu den Tabellen in einem Schema. Es sagt nichts
darüber, welche dieser Tabellen die Rolle benutzen darf oder welche Zeilen sie
zurückbekommt.
Hinter der Eingangstür stehen zwei weitere Prüfungen, und beide gehören zur
jeweiligen Tabelle. Der eigene Grant der Tabelle entscheidet, ob eine Rolle diese
Tabelle überhaupt benutzen darf, und ein fehlender ist das, was permission denied for table meldet. Danach entscheidet Row Level Security, die Regeln pro
Zeile, die du vielleicht schon kennst, welche Zeilen diese Rolle zurückbekommt.
Postgres fragt alle drei in dieser Reihenfolge ab und hört bei der ersten
Ablehnung auf.
Genau diese Reihenfolge macht den Grant auf das Schema zu einem schlechten
Schloss. Für eine Rolle heißt er alles oder nichts: Nimm ihn anon weg, und
jeder Bildschirm, den ein Besucher ohne Anmeldung sieht, hört auf einmal auf zu
funktionieren, auch eine Anmeldeseite, die irgendetwas aus deinen Tabellen liest.
Nimm ihn authenticated weg, und dasselbe passiert allen Angemeldeten. Die
Dokumentation von Postgres fügt hinzu, dass sein Entzug "is not a completely
secure way to prevent object access", also kein vollständig sicherer Weg ist,
den Zugriff zu verhindern, und dass eine Rolle ohne ihn die Namen deiner Tabellen
weiterhin aus den Systemkatalogen lesen kann.
Wenn du diesen Fehler überhaupt siehst, braucht deine App die Rolle, die abgewiesen wurde.
Ursache 1: Das Schema wurde gelöscht und neu angelegt
Diese Ursache nimmt am meisten mit. Jemand hat diese zwei Zeilen ausgeführt oder etwas, das dasselbe tut:
drop schema public cascade;
create schema public;
Die erste Zeile entfernt das Schema und wegen cascade jede Tabelle, jede View
und jede Funktion darin. Die zweite legt ein neues, leeres public an. Ein neues
Schema gehört der Rolle, die es angelegt hat, und die Dokumentation von Postgres
sagt klar, dass sonst niemand standardmäßig irgendein Recht auf ein Schema
bekommt. Der Grant, den Supabase beim Anlegen deines Projekts ausgeführt hat,
gehörte zum alten Schema und ist mit ihm verschwunden, und Supabase führt sein
Setup-Skript kein zweites Mal aus.
Diese zwei Zeilen sind der kürzeste Weg, eine Postgres-Datenbank zu leeren. Ein KI-Assistent greift deshalb womöglich zu ihnen, wenn du ihn bittest, die Datenbank neu aufzusetzen oder eine fehlschlagende Migration sauber durchlaufen zu lassen.
Danach kommen deine Tabellen zurück, wenn die Migrationen der App erneut laufen. Eine Migration ist eine der Dateien mit Datenbankänderungen, die dein Builder schreibt, und wenn alle laufen, bauen sie die Tabellen wieder auf, die sie beschreiben. Was nicht zurückkommt, ist alles, was sie nie erwähnen, und den Grant für die Eingangstür hat Supabase angelegt, bevor deine erste Migration existierte. In Migrationen, die dein Builder geschrieben hat, kommt er womöglich gar nicht vor.
Zwei weitere Dinge verschwinden mit dem alten Schema:
- Die automatischen Grants für neue Tabellen. Postgres speichert sie als Standardrechte (default privileges) eines Schemas, und sie werden zusammen mit dem Schema gelöscht. Tabellen, die danach entstehen, haben für keine der Rollen deiner App einen Grant.
- Die Zeilen. Migrationen bauen leere Tabellen. Was in den alten stand, ist nur dann wieder da, wenn jemand eine Kopie eingespielt hat, die vor dem Reset entstanden ist.
Wenn deine Tabellen zurück und leer sind, kommt die Suche nach dieser Kopie vor allem anderen auf dieser Seite.
Ursache 2: Ein Entzug, der absichern sollte
Der zweite Weg ist Absicht. Jemand hat gelesen, dass jede Tabelle in public
über die Data API von Supabase erreichbar ist, also über die Webadresse, über
die deine App ihre Daten liest und schreibt, und hat den Grant auf das Schema
entzogen, um das zu schließen.
Die Sorge dahinter ist berechtigt, das gewählte Werkzeug ist das falsche. Der
eigene Leitfaden von Supabase zum
Absichern der Data API
fasst den Grant auf das Schema nie an. Er entzieht den automatischen Zugriff,
den neue Tabellen in public bekommen, sodass jede neue Tabelle zu bleibt, bis
jemand ihr absichtlich Zugriff gibt. Supabase schaltet das am 30. Oktober 2026
für jedes bestehende Projekt ein.
Hat ein Skript, das du ausgeführt hast, oder ein Assistent, den du um mehr Sicherheit gebeten hast, den Grant auf das Schema entfernt, zeigt dir die Prüfabfrage oben, welche Rollen ihn verloren haben. Gib ihn den Rollen zurück, die deine App benutzt, und sichere dann so ab, wie es die Abschnitte weiter unten beschreiben.
Ursache 3: Der Fehler kommt aus einer Migration
Erscheint die Meldung, während etwas Tabellen anlegt, etwa eine Migration, ein
Deployment oder Prisma oder Drizzle beim Einrichten deiner Datenbank, dann fehlt
ein anderes Recht. Um irgendetwas in einem Schema anzulegen, braucht es CREATE
darauf, und die Fehlermeldung für ein fehlendes CREATE ist dieselbe Zeile.
Seit Postgres 15 bekommen gewöhnliche Rollen standardmäßig kein CREATE mehr auf
public. Die
Dokumentation von Postgres
sagt, dass nur Datenbanken, die von Version 14 oder älter aktualisiert wurden,
es noch allen geben. Ein eigener Datenbanknutzer für ein Migrationswerkzeug wird
deshalb schon bei seiner allerersten Tabelle abgewiesen. Der
Prisma-Leitfaden von
Supabase legt einen Nutzer prisma an, und zu seinem Setup gehört
grant create on schema public to prisma.
Die Lösung ist ein create-Grant für genau diesen einen Migrationsnutzer.
Niemand, der deine App besucht, muss Tabellen anlegen, deshalb bekommen anon
und authenticated nie einen.
So holst du den Grant sicher zurück
Führ eine Zeile aus, prüf, dass alle drei Rollen hineinkommen, lad dann deine App neu und lies, was sie als Nächstes sagt.
Die Zeile, im SQL Editor:
grant usage on schema public to anon, authenticated, service_role;
Bleib bei usage. grant all on schema public beseitigt den Fehler auch, und
auf einem Schema heißt all so viel wie usage plus create, also das Recht,
eigene Tabellen hinzuzufügen. Supabase rät, jeder Rolle
nur das Nötigste zu
geben, und die Rollen deiner Besucher müssen nur hineinkommen.
Die Prüfabfrage von oben sollte jetzt für alle drei Rollen true zeigen. Lad
deine App neu, und eins von zwei Dingen passiert:
- Sie funktioniert. Die Eingangstür war das Einzige, was gefehlt hat.
- Eine neue Meldung nennt eine Tabelle.
permission denied for tableheißt, dass auch der eigene Grant dieser Tabelle fehlt. Damit ist nach der ersten Ursache zu rechnen, denn neu gebaute Tabellen haben die automatischen Grants nie bekommen. Gib jeder Tabelle ihren Grant, Row Level Security und eine Regel in einer Änderung.
Widersteh der Versuchung, jeder Rolle alles auf jeder Tabelle zu geben, damit die
zweite Meldung verschwindet. Eine Tabelle mit Grant an anon und ausgeschalteter
Row Level Security gibt jede Zeile an jeden heraus, der danach fragt, und in
deiner App sieht nichts anders aus.
Nennt die Meldung ein anderes Schema, etwa permission denied for schema api,
dann hat jemand dieses Schema selbst angelegt, und es hat nie die Grants
bekommen, mit denen public startet. Der Leitfaden von Supabase zu
eigenen Schemas
listet die Zeilen auf, die es braucht.
Was du stattdessen absichern solltest
Die Mittel, die Fremde von deinen Daten fernhalten, gehören zu jeder einzelnen Tabelle, und der Sicherheitsleitfaden von Supabase nennt sie:
- Ein Grant pro Tabelle, für die Rollen, die ihn brauchen. Eine Tabelle mit
Bestellungen braucht für
anonüberhaupt keinen Grant. - Row Level Security auf jeder Tabelle, mit einer Regel.Wie du sie einschaltest und prüfst, ob sie hält.
- Neue Tabellen standardmäßig zu. Das Entziehen der automatischen Grants erledigt das schon heute, und der 30. Oktober erledigt es für dich.
- Interne Tabellen außerhalb der Data API. Der Leitfaden von Supabase beschreibt, das, was deine App liest, in ein eigenes Schema zu legen, sodass alles andere in Schemas liegt, die die Data API nicht ausliefert.
Schau dir das Ergebnis danach von außen an, denn keiner dieser Bildschirme zeigt dir, was ein Fremder zurückbekommt. Unser kostenloser Scan fragt jede Tabelle, die deine App nennt, wie viele Zeilen ein Besucher ohne Anmeldung bekäme, liest die Zahl, ohne eine einzige Zeile abzurufen, und dauert ohne Konto etwa 20 Sekunden: scanne deine App.
Deine Tabellen behalten, und was in ihnen steht
Nach heute kann noch zweierlei schiefgehen: Eine spätere Änderung öffnet eine Tabelle für Fremde, und ein Reset wie bei der ersten Ursache nimmt deine Zeilen mit.
Reeve Monitor prüft weiter von außen:
- die vollständige Sicherheitsprüfung jede Stunde, für bis zu drei Apps
- eine Nachricht, wenn sich ein Ergebnis ändert, damit eine Tabelle, die nach einer Migration aufgegangen ist, nicht wartet, bis du hinschaust
- ob deine App erreichbar ist
- ein monatlicher Bericht in einfacher Sprache über das, was er gesehen hat
Reeve Care hält eine Kopie von dem, was in deinen Tabellen steht:
- eine verschlüsselte Kopie deiner Supabase-Datenbank nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
- jede Kopie geprüft, bevor sie zählt, durch Zählen der Zeilen in jeder Tabelle
- auch deine hochgeladenen Dateien, sobald du einen Storage-Zugang verbindest
- eine Wiederherstellung per Klick, mit einer Sicherheitskopie des aktuellen Stands vorab
- alles, was Monitor tut
Was du jetzt tun solltest
Was zu tun ist
- Führ die Prüfabfrage im SQL Editor aus. Jede Rolle mit
falseist eine, die deine App verloren hat. - Hol den Grant mit
grant usage on schema public to anon, authenticated, service_role;zurück und bleib beiusage. - Wurde das Schema gelöscht, sieh nach, ob deine Tabellen leer sind, und such eine Kopie von vor dem Reset.
- Folgt ein Tabellen-Fehler, gib dieser Tabelle ihren Grant, Row Level Security und eine Regel zusammen.
- Sichere pro Tabelle ab, wo es sich fein einstellen lässt, und lass den Grant auf das Schema in Ruhe.
- Kam der Fehler aus einer Migration, gib
createdem Migrationsnutzer und sonst niemandem.
Fang mit der Prüfabfrage an, denn ein einziges Ergebnis sagt dir, welche Rollen deiner App den Weg hinein verloren haben. Und wenn die Tür wieder offen ist, scanne deine App, um zu sehen, was sie einem Fremden gibt.
FAQ
Was bedeutet "permission denied for schema public" in Supabase?
Es bedeutet, dass die Rolle hinter der Anfrage, meist anon oder authenticated, das Schema public nicht betreten darf. Postgres hat also abgelehnt, bevor es irgendeine Tabelle angesehen hat. Der Fehler trägt den Code 42501 und erreicht deine App meist als 401 oder 403. Jedes Supabase-Projekt startet mit diesem Recht, der Fehler heißt also, dass jemand oder etwas es entfernt hat.
Ist es sicher, usage auf das Schema public zu vergeben?
Ja. Jedes Supabase-Projekt startet mit genau diesem Grant. Er lässt eine Rolle nur bis zu den Tabellen im Schema: Jede Tabelle braucht weiterhin ihren eigenen Grant, und Row Level Security entscheidet weiterhin, welche Zeilen jeder Besucher bekommt. Einen Blick verdienen die Tabellen dahinter, denn eine mit Grant an anon und ausgeschalteter Row Level Security gibt jede Zeile an jeden heraus, der danach fragt.
Was ist der Unterschied zu "permission denied for table"?
Der Schema-Fehler stoppt eine Anfrage, bevor sie irgendeine Tabelle erreicht, deshalb schlagen alle Tabellen im Schema auf einmal fehl. Der Tabellen-Fehler betrifft eine einzelne Tabelle, die für diese Rolle keinen Grant hat, und der Rest deiner App läuft weiter. Beide tragen den Code 42501, unterscheiden lassen sie sich also nur am Wortlaut der Meldung.
Warum funktionieren meine Tabellen im Supabase SQL Editor, aber nicht in meiner App?
Weil der SQL Editor deine Abfragen als Rolle postgres ausführt. Ihr gehören deine Tabellen, und sie braucht den Grant nicht, den die Rollen deiner App verloren haben. Deine App benutzt anon für Besucher ohne Anmeldung und authenticated für angemeldete. Frag Postgres mit has_schema_privilege direkt nach diesen beiden Rollen, dann siehst du, was deine App sieht.
Was sollte ich stattdessen absichern?
Die Tabellen. Gib jeder Tabelle nur für die Rollen einen Grant, die ihn brauchen, schalte Row Level Security mit einer Regel auf jeder Tabelle ein und lass neue Tabellen ganz ohne Zugriff starten. Diesen letzten Teil macht Supabase am 30. Oktober 2026 für bestehende Projekte zum Standard.