Sicherheitsgrundlagen
New row violates row-level security policy in Supabase: was jetzt?
"New row violates row-level security policy" heißt, Supabase hat einen Schreibvorgang abgelehnt. Die schnelle Lösung öffnet die Tabelle wieder für alle.

Kurz gesagt
- New row violates row-level security policy heißt: Deine Datenbank hat die Regeln dieser Tabelle geprüft und keine gefunden, die die Zeile erlaubt, die du speichern wolltest. Sie hat den Schreibvorgang abgelehnt und die Tabelle so gelassen, wie sie war.
- Eine Meldung deckt drei verschiedene Situationen ab: gar keine Regeln, eine Regel, die nur vom Lesen handelt, oder eine Regel zum Schreiben, die deine Zeile nicht erfüllt.
- Die Antwort ganz oben in jeder Suche beendet den Fehler, indem sie jedes Schreiben von jedem erlaubt. Die Regel, die du eigentlich willst, kostet rund zwei Minuten mehr.
Irgendetwas hat dir geraten, Row Level Security anzuschalten. Der Advisor in deinem Supabase-Dashboard, ein Scan-Ergebnis, eine Checkliste, jemand in einem Discord. Du hast es angeschaltet. Jetzt kann deine App überhaupt nichts mehr speichern. Jeder Versuch kommt mit der Meldung zurück, dass a new row violates row-level security policy:
new row violates row-level security policy for table "profiles"
Hier ist der Punkt, den Anleitung um Anleitung falsch darstellt: Die Antwort, die diesen Fehler in zehn Sekunden beendet, macht genau das rückgängig, was du gerade angeschaltet hast. Sie ist das erste Ergebnis, das du findest, sie wirkt sofort, und sie lässt die Tabelle exakt dort, wo sie war, bevor dir jemand geraten hat, sie in Ordnung zu bringen.
Was "new row violates row-level security policy" bedeutet
Deine Datenbank sollte eine Zeile speichern, hat die Regeln dieser Tabelle geprüft, keine gefunden, die genau diese Zeile erlaubt, und abgelehnt.
Das ist die ganze Meldung. Nichts ist beschädigt und nichts ist verloren: Die
Zeile wurde nie gespeichert, und der Rest der Tabelle steht genau so da wie vor
einer Minute. Der Wortlaut kommt von Postgres, der Datenbank-Engine, auf der
Supabase läuft, und deshalb liest er sich nach Maschine statt nach irgendetwas,
das dein Builder geschrieben hat. Supabase reicht ihn mit seiner Nummer weiter,
42501.
Was die Meldung dir nicht sagt, ist, welche Regel gefehlt hat. Ein Satz deckt drei verschiedene Situationen ab, und dir zu sagen, in welcher du steckst, würde demjenigen deine Regeln beschreiben, der den Fehler ausgelöst hat:
- Die Tabelle hat Row Level Security an und gar keine Regeln geschrieben.
- Sie hat eine Regel, und die handelt nur vom Lesen.
- Sie hat eine Regel zum Schreiben, und die Zeile, die du geschickt hast, erfüllt sie nicht.
Alle drei geben dieselbe Zeile aus. Die zweite ist die häufige bei einer frisch gestarteten App, wo eine Regel ergänzt wurde, damit die Bildschirme wieder laufen, und vom Speichern nie die Rede war.
Warum er genau beim Anschalten von Row Level Security auftauchte
Weil Anschalten alles ablehnt, bis eine Regel etwas anderes sagt, und deine eigene App zu allem dazugehört.
Das ist der Ablauf, den fast jeder durchmacht. Du schaltest die Einstellung an. Die Bildschirme bleiben leer, denn ohne geschriebene Regeln gibt die Datenbank jetzt niemandem mehr Zeilen heraus, deiner App eingeschlossen. Du ergänzt eine Regel, damit die Listen zurückkommen, meist die erste, die ein Suchergebnis oder dein Builder vorschlägt. Die Bildschirme füllen sich. Und beim ersten Mal, wenn jemand auf Speichern drückt, erscheint dieser Fehler, bei einer Tabelle, die du für erledigt gehalten hast.
In diesem Ablauf ist nichts schiefgegangen. Die Regel, die du ergänzt hast, handelte vom Lesen, und Speichern ist eine andere Frage, die bis dahin niemand gestellt hatte.
Eine Regel hat zwei Hälften, und nur eine handelt vom Lesen
Eine Policy ist eine Bedingung, und worauf die Datenbank diese Bedingung anwendet, hängt davon ab, worum du sie gebeten hast.
USING wird auf Zeilen angewendet, die schon in der Tabelle liegen. Es
entscheidet, welche du sehen, ändern oder entfernen darfst. WITH CHECK wird
auf die Zeile angewendet, die du anlegen willst, bevor sie irgendwo existiert.
Es entscheidet, ob diese Zeile überhaupt entstehen darf.
Die zweite Hälfte ist der Teil, der Menschen überrascht, denn sie ist eine Regel über etwas, das noch nicht da ist.
| Deine App möchte | Geprüft an Zeilen, die schon in der Tabelle stehen | Geprüft an der Zeile, die geschrieben wird |
|---|---|---|
lesen (select) | USING | nichts zu prüfen |
eine neue Zeile speichern (insert) | nichts zu prüfen | WITH CHECK |
eine Zeile ändern (update) | USING | WITH CHECK |
eine Zeile entfernen (delete) | USING | nichts zu prüfen |
Eine Policy mit FOR SELECT trägt immer nur die erste Hälfte, denn beim Lesen
gibt es keine neue Zeile zu prüfen. Sie kann also nie ein Speichern erlauben, so
großzügig sie auch aussieht. Das ist der ganze Bruch, und er erklärt, warum dein
Lesen sich erholt hat und dein Schreiben nicht.
Wenn du das lieber von außen sehen möchtest: Unser kostenloser Scan fragt deine Datenbank live, was ein Fremder schon jetzt aus ihr lesen kann, ohne Konto und in rund 20 Sekunden: App scannen.
Die Lösung, die den Fehler beendet, und was sie kostet
Am schnellsten verschwindet er mit einer Regel, die jedes Schreiben von jedem erlaubt, und genau deshalb ist sie fast überall die oberste Antwort.
Sie kommt meist so daher:
CREATE POLICY "Enable insert for all users"
ON public.profiles
FOR INSERT
WITH CHECK (true);
Jede Zeile erfüllt true, also ist jedes Speichern erlaubt, von deiner App und
von jedem anderen, der den Schlüssel hat, der darin mitgeliefert wird. Row Level
Security wieder abzuschalten leistet dasselbe noch gründlicher.
Beides funktioniert. Beides lässt dich dort, wo du vor dem Hinweis des Advisors warst, und danach zeigt keines von beiden noch an, dass etwas offen ist, denn deine App verhält sich in allen vier Kombinationen aus Einstellung und Regel gleich. Eine Tabelle kann angeschaltet sein, eine gültige Policy tragen, ein grünes Abzeichen im Dashboard zeigen und ihre Zeilen trotzdem an einen Fremden herausgeben. Das ist die andere Hälfte dieses Problems, und sie ist es wert, gelesen zu werden, bevor du irgendetwas einfügst.
Die Regel, die deine App speichern lässt, und nur deine App
Benenne, wem die Zeile gehört, und vergleiche das mit dem, der gerade fragt:
CREATE POLICY "Users insert their own rows"
ON public.profiles
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
auth.uid() ist, wer angemeldet ist und die Anfrage stellt. user_id ist die
Spalte auf der Zeile, die sagt, wem sie gehört. Stimmen die beiden überein, wird
die Zeile gespeichert. Tun sie es nicht, oder ist niemand angemeldet, wird sie
abgelehnt, und wer abgelehnt wird, sieht dieselbe Meldung, die du gerade
anschaust.
Zwei Details in dieser Regel verdienen ihren Platz. TO authenticated heißt,
dass die Regel für angemeldete Besucher gilt; lässt du es weg, gilt die Policy
für alle, Fremde eingeschlossen. Und deine App muss die Spalte user_id
tatsächlich mitschicken, denn eine Regel, die auth.uid() mit einem leer
angekommenen Wert vergleicht, kann nie übereinstimmen.
Wenn die Regel stimmt und der Fehler trotzdem kommt
Schau, wer angemeldet war, bevor du dir die Policy noch einmal ansiehst.
Drei Erklärungen decken das meiste ab, was übrig bleibt:
Niemand ist angemeldet. auth.uid() kommt bei einem Besucher, der sich nie
angemeldet hat, leer zurück, also kann eine Regel, die es mit einer
Besitzerspalte vergleicht, nicht übereinstimmen. Bei einem
Registrierungsformular, einer Warteliste oder einem Kontaktformular ist das
normal, und die brauchen eine eigene Regel, die beschreibt, was ein Fremder
ergänzen darf.
Die Besitzerspalte kommt nie an. Deine Regel vergleicht auth.uid() mit
user_id, und deine App schickt alles außer user_id. Der Vergleich läuft
gegen eine Leerstelle und scheitert jedes Mal.
Der Schreibvorgang geht an Storage. Datei-Uploads landen in Supabase
Storage, das eigene Policies auf storage.objects führt statt auf deiner
Tabelle. Eine Regel auf profiles sagt nichts über eine Datei.
Es gibt eine vierte Erklärung, und die schließt man am besten früh aus: Wenn das Speichern in der Vorschau deines Builders klappt und auf deiner Live-Seite scheitert, benutzen beide nicht denselben Schlüssel. Ein geheimer Schlüssel ignoriert jede Regel, die du geschrieben hast, dafür ist er gedacht. Eine App, die nur speichert, solange ein geheimer Schlüssel im Spiel ist, ist eine App, deren Regeln nie wirklich geprüft wurden. Welche API-Schlüssel im Frontend sicher sind zeigt, wie du den einen vom anderen unterscheidest.
Was du heute tun solltest
Was zu tun ist
- Lies den Fehler als Ablehnung statt als Defekt. Der Schreibvorgang hat nicht stattgefunden, die Tabelle ist unverändert, und nichts muss wiederhergestellt werden.
- Prüfe, ob die Tabelle überhaupt eine Policy zum Schreiben hat. Eine Regel mit
FOR SELECThat deine Bildschirme repariert und nichts über das Speichern gesagt. - Ergänze eine Policy
FOR INSERT, derenWITH CHECK-Bedingung den Besitzer der Zeile benennt, und ergänze dieTO-Klausel, die du gemeint hast. - Stelle sicher, dass deine App die Besitzerspalte wirklich mitschickt, und versuche das Speichern dann angemeldet erneut.
- Lass
WITH CHECK (true)für Tabellen stehen, deren Inhalt dir auf einer öffentlichen Seite recht wäre. Bei allem, in dem ein Mensch vorkommt, nimm dir die zwei Minuten.
Fang mit der Tabelle an, aus der der Fehler kam, und prüfe jede andere Tabelle, bei der du die Einstellung am selben Nachmittag angeschaltet hast. Die 10-Minuten-Sicherheitscheckliste deckt ab, was bei einer frisch gestarteten App sonst noch offen zu bleiben pflegt, und der Supabase-Sicherheitsleitfaden geht durch, woran ein Fremder sonst noch herankommt.
FAQ
Was bedeutet "new row violates row-level security policy"?
Es bedeutet, dass deine Datenbank eine Zeile speichern sollte, die Regeln dieser Tabelle geprüft hat und keine gefunden hat, die diese Zeile erlaubt. Also hat sie den Schreibvorgang abgelehnt. Nichts ist verloren und nichts ist kaputt, denn die Zeile wurde nie gespeichert und der Rest der Tabelle ist unberührt. Die Meldung kommt von Postgres, der Datenbank-Engine, auf der Supabase läuft, und sie trägt den Code 42501. Was sie bewusst nicht sagt, ist, welche Regel gefehlt hat, denn das würde demjenigen deine Regeln beschreiben, der den Fehler ausgelöst hat.
Ich habe eine Policy ergänzt und Lesen funktioniert. Warum scheitert Speichern trotzdem?
Weil eine Policy zum Lesen nichts über das Schreiben sagt. Eine Regel hat eine USING-Hälfte, die die Datenbank auf Zeilen anwendet, die schon in der Tabelle stehen, und eine WITH CHECK-Hälfte, die sie auf die Zeile anwendet, die du anlegen willst. Eine Policy mit FOR SELECT hat immer nur die erste, denn beim Lesen gibt es keine neue Zeile zu prüfen. Deine Bildschirme füllen sich wieder und das erste Speichern scheitert weiterhin. Ergänze eine zweite Policy FOR INSERT mit einer WITH CHECK-Bedingung.
Soll ich Row Level Security einfach abschalten, damit das weggeht?
Das beendet den Fehler, und es lässt jede Zeile dieser Tabelle für jeden lesbar, der den Schlüssel hat, der in deiner App mitgeliefert wird. Deine App läuft in beiden Fällen, also sagt dir danach nichts mehr, welche der beiden Varianten du gewählt hast. Wenn in der Tabelle Menschen, Bestellungen oder Nachrichten stehen, sind die zwei Minuten für eine echte Regel der Unterschied zwischen einer privaten und einer öffentlichen Tabelle.
Meine Policy sieht richtig aus und Inserts scheitern trotzdem. Was kann es sonst sein?
Drei Dinge erklären die meisten Fälle. Niemand ist angemeldet, also ist auth.uid() leer und eine Regel, die es mit einer Besitzerspalte vergleicht, kann nie übereinstimmen, was bei einem Registrierungs- oder Kontaktformular normal ist. Oder deine App schickt die Besitzerspalte gar nicht mit, sodass die Regel gegen einen leeren Wert vergleicht. Oder der Schreibvorgang geht in Supabase Storage statt in eine Tabelle, und Storage führt eigene Policies auf storage.objects.
Heißt dieser Fehler, dass jemand meine App angegriffen hat?
Fast nie. Bei einer frisch gestarteten App ist es fast immer deine eigene App, die abgewiesen wird, weil Row Level Security angeschaltet wurde, bevor eine Regel für deine eigenen Schreibvorgänge existierte. Trotzdem lohnt es sich, sie zu lesen statt sie wegzuklicken: Dieselbe Meldung erscheint, wenn eine Anfrage abgewiesen wird, die gar nicht in diese Tabelle schreiben sollte, und an der Meldung allein kannst du beides nicht unterscheiden.