[{"data":1,"prerenderedAt":1372},["ShallowReactive",2],{"blog-de-duplicate-key-value-violates-unique-constraint":3,"blog-index-de":844},{"id":4,"title":5,"body":6,"category":802,"cover":803,"coverAlt":804,"description":805,"draft":806,"extension":807,"faq":808,"image":823,"keywords":824,"meta":833,"navigation":834,"ogTitle":5,"path":835,"published":836,"seo":837,"stem":838,"tldr":839,"updated":836,"__hash__":843},"blog_de\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint.md","Supabase \"duplicate key value violates unique constraint\" erklärt",{"type":7,"value":8,"toc":790},"minimark",[9,13,24,31,34,39,42,45,48,167,180,184,193,267,270,278,292,296,299,302,324,337,343,366,379,385,389,392,399,407,413,419,432,443,447,450,478,515,521,541,557,560,565,569,572,593,608,616,632,635,641,664,669,673,676,685,708,717,735,739,780],[10,11,12],"p",{},"Jemand füllt das Formular für deine Warteliste aus, wartet und drückt den Knopf\nnoch einmal. Der erste Klick war schon durchgegangen. Der zweite kommt als Fehler\nzurück, und die Browserkonsole zeigt alles:",[14,15,20],"pre",{"className":16,"code":18,"language":19},[17],"language-text","{\n  \"code\": \"23505\",\n  \"details\": \"Key (email)=(maria@example.com) already exists.\",\n  \"hint\": null,\n  \"message\": \"duplicate key value violates unique constraint \\\"waitlist_email_key\\\"\"\n}\n","text",[21,22,18],"code",{"__ignoreMap":23},"",[10,25,26,27,30],{},"Supabase \"duplicate key value violates unique constraint\" heißt, dass Postgres die\nZeile abgelehnt hat, weil eine andere Zeile denselben Wert schon in einer Spalte\nhat, die jeden Wert nur einmal erlaubt. Hier ist die Spalte ",[21,28,29],{},"email",", und Maria\nsteht schon auf deiner Liste.",[10,32,33],{},"Der Rat, der für diesen Fehler herumgereicht wird, lautet: das Insert auf ein\nUpsert umstellen. Manchmal stimmt das. Auf einer Tabelle, in die Besucher nur\neintragen dürfen, braucht ein Upsert aus dem Browser allerdings Regeln, die ihnen\ndas Lesen der Tabelle erlauben, und die Zeile mit Marias Adresse sollte deine App\neinem Besucher überhaupt nie zeigen.",[35,36,38],"h2",{"id":37},"was-bedeutet-duplicate-key-value-violates-unique-constraint","Was bedeutet \"duplicate key value violates unique constraint\"?",[10,40,41],{},"Der Wert, den du speichern wolltest, steht schon in einer Spalte, die jeden Wert\nnur einmal nimmt, also hat Postgres die ganze Zeile abgelehnt. Geschrieben wurde\nnichts, und die Zeile, die zuerst da war, ist unverändert.",[10,43,44],{},"Stell dir die Tabelle als Gästeliste an einer Tür vor, eine Zeile pro Name. Eine\nUnique-Regel ist die Regel des Türstehers, dass ein Name nur einmal auf die Liste\nkommt. Deine App kam mit einem Namen, der schon dasteht, und er hat sie\nabgewiesen.",[10,46,47],{},"Jeder Teil des Fehlers sagt eine Sache:",[49,50,51,67],"table",{},[52,53,54],"thead",{},[55,56,57,61,64],"tr",{},[58,59,60],"th",{},"Feld",[58,62,63],{},"Beispiel",[58,65,66],{},"Was es dir sagt",[68,69,70,96,111,126,141],"tbody",{},[55,71,72,77,82],{},[73,74,75],"td",{},[21,76,21],{},[73,78,79],{},[21,80,81],{},"23505",[73,83,84,85,88,89],{},"Der Code von Postgres für diesen Fehler, ",[21,86,87],{},"unique_violation"," in ",[90,91,95],"a",{"href":92,"rel":93},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Futils\u002Ferrcodes.txt",[94],"nofollow","seiner Liste der Codes",[55,97,98,103,108],{},[73,99,100],{},[21,101,102],{},"message",[73,104,105],{},[21,106,107],{},"duplicate key value violates unique constraint \"waitlist_email_key\"",[73,109,110],{},"Welche Regel die Zeile abgelehnt hat, mit Namen",[55,112,113,118,123],{},[73,114,115],{},[21,116,117],{},"details",[73,119,120],{},[21,121,122],{},"Key (email)=(maria@example.com) already exists.",[73,124,125],{},"Spalte und Wert, die kollidiert sind, wenn Postgres entscheidet, dass der Aufrufer sie sehen darf",[55,127,128,133,138],{},[73,129,130],{},[21,131,132],{},"hint",[73,134,135],{},[21,136,137],{},"null",[73,139,140],{},"Nichts hinzuzufügen bei diesem Fehler",[55,142,143,146,151],{},[73,144,145],{},"HTTP-Status",[73,147,148],{},[21,149,150],{},"409 Conflict",[73,152,153,154,157,158,160,161,166],{},"So meldet PostgREST, der Teil von Supabase, der Aufrufe ",[21,155,156],{},"supabase.from(...)"," in SQL übersetzt, den ",[21,159,81],{}," (",[90,162,165],{"href":163,"rel":164},"https:\u002F\u002Fgithub.com\u002FPostgREST\u002Fpostgrest\u002Fblob\u002Fmain\u002Fsrc\u002Flibrary\u002FPostgREST\u002FError.hs",[94],"sein Code",")",[10,168,169,170,175,176,179],{},"Der Wortlaut stammt von Postgres selbst.\n",[90,171,174],{"href":172,"rel":173},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Faccess\u002Fnbtree\u002Fnbtinsert.c",[94],"Der Code, der den Fehler auslöst",",\nhängt die Zeile ",[21,177,178],{},"Key ... already exists"," nur an, wenn er eine Beschreibung des\nSchlüssels hat, und ein Abschnitt weiter unten zeigt, wann er keine hat.",[35,181,183],{"id":182},"welche-spalte-hat-den-fehler-ausgelöst","Welche Spalte hat den Fehler ausgelöst?",[10,185,186,187,192],{},"Das sagt der Name in Anführungszeichen, denn Postgres baut ihn aus Tabelle und\nSpalten, solange niemand einen anderen gewählt hat.\n",[90,188,191],{"href":189,"rel":190},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Fcommands\u002Findexcmds.c",[94],"Sein Code für die Namen","\nkennt drei Formen:",[49,194,195,207],{},[52,196,197],{},[55,198,199,202,205],{},[58,200,201],{},"Der Name endet auf",[58,203,204],{},"Es ist",[58,206,63],{},[68,208,209,233,251],{},[55,210,211,216,221],{},[73,212,213],{},[21,214,215],{},"_key",[73,217,218,219],{},"eine Unique-Regel auf den Spalten zwischen Tabellenname und ",[21,220,215],{},[73,222,223,226,227,230,231],{},[21,224,225],{},"waitlist_email_key",": Tabelle ",[21,228,229],{},"waitlist",", Spalte ",[21,232,29],{},[55,234,235,240,246],{},[73,236,237],{},[21,238,239],{},"_pkey",[73,241,242,243],{},"der Primärschlüssel der Tabelle, meist ",[21,244,245],{},"id",[73,247,248],{},[21,249,250],{},"orders_pkey",[55,252,253,259,262],{},[73,254,255,258],{},[21,256,257],{},"_idx"," oder etwas anderes",[73,260,261],{},"ein von Hand angelegter Unique-Index oder eine Regel mit eigenem Namen",[73,263,264],{},[21,265,266],{},"profiles_username_idx",[10,268,269],{},"Die Meldung sagt bei allen dreien \"unique constraint\". Um genau zu sehen, was ein\nName abdeckt, führ das im SQL Editor aus, mit dem Namen aus deiner Meldung:",[14,271,276],{"className":272,"code":274,"language":275,"meta":23},[273],"language-sql","select tablename, indexdef\nfrom pg_indexes\nwhere indexname = 'waitlist_email_key';\n","sql",[21,277,274],{"__ignoreMap":23},[10,279,280,283,284,287,288,291],{},[21,281,282],{},"indexdef"," kommt etwa als\n",[21,285,286],{},"CREATE UNIQUE INDEX waitlist_email_key ON public.waitlist USING btree (email)","\nzurück, und die Spalten in den Klammern sind die, die sich nicht wiederholen\ndürfen. Eine Regel auf zwei Spalten, etwa ",[21,289,290],{},"(team_id, email)",", lässt dieselbe\nAdresse in zwei Teams zu und lehnt sie im selben Team beim zweiten Mal ab.",[35,293,295],{"id":294},"key-id1-already-exists-obwohl-du-nie-eine-id-geschickt-hast","\"Key (id)=(1) already exists\", obwohl du nie eine id geschickt hast",[10,297,298],{},"Hier hinkt der ID-Zähler der Tabelle den Zeilen hinterher, die schon drin sind,\nalso hat er eine Nummer vergeben, die besetzt ist. Deine App hat keine id\ngeschickt. Postgres hat die nächste Nummer vom Zähler genommen, und eine Zeile mit\ndieser Nummer gab es schon.",[10,300,301],{},"Auf der Gästeliste verteilt der Türsteher auch nummerierte Armbänder von einer\nRolle. Jemand hat die ersten zweihundert Gäste von Hand eingetragen, samt\nArmbandnummern, und niemand hat die Rolle weitergedreht. Sie fängt immer noch bei\n1 an.",[10,303,304,305,310,311,316,317,319,320,323],{},"Eine Spalte, die sich selbst nummeriert, nimmt jede Nummer aus einer Sequenz, so\nnennt Postgres diesen Zähler. Postgres'\n",[90,306,309],{"href":307,"rel":308},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-identity-columns.html",[94],"Seite über Identity-Spalten","\nsagt, eine solche Spalte \"does not guarantee uniqueness\", und nennt genau diesen\nFall als einen der Gründe: Werte \"could be inserted manually into the identity\ncolumn\". Zeilen kommen mit schon ausgefüllten Nummern an, aus einem CSV-Import mit\nder Spalte id, einer Kopie aus einem anderen Projekt oder einem Seed-Skript. Der\nZähler erfährt von keiner davon.\n",[90,312,315],{"href":313,"rel":314},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Ftroubleshooting\u002Fwhy-are-there-gaps-in-my-postgres-id-sequence-Frifus",[94],"Supabases Troubleshooting-Seite über ID-Sequenzen","\nbehandelt genau diese Meldung bei einem ",[21,318,239],{},", und ihre Lösung ist ebenfalls ein\n",[21,321,322],{},"setval",".",[10,325,326,327,330,331,333,334,336],{},"Die Reparatur ist eine Anweisung. Setz deine eigene Tabelle und Spalte statt\n",[21,328,329],{},"orders"," und ",[21,332,245],{}," ein; die Tabelle ist der Teil des Namens vor ",[21,335,239],{},":",[14,338,341],{"className":339,"code":340,"language":275,"meta":23},[273],"select setval(\n  pg_get_serial_sequence('public.orders', 'id'),\n  (select max(id) from public.orders)\n);\n",[21,342,340],{"__ignoreMap":23},[10,344,345,348,349,354,355,361,362,323],{},[21,346,347],{},"pg_get_serial_sequence"," findet den Zähler hinter der Spalte, und\n",[90,350,353],{"href":351,"rel":352},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions-info.html",[94],"die Dokumentation von Postgres","\nsagt, dass das für beide Arten funktioniert: \"If the column is an identity column,\nthe associated sequence is the sequence internally created for that column.\"\nDanach stellt ",[90,356,359],{"href":357,"rel":358},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Ffunctions-sequence.html",[94],[21,360,322],{},"\nmit zwei Argumenten den Zähler so, dass \"the next nextval will advance the\nsequence before returning a value\". Die nächste neue Zeile bekommt die höchste id\nplus eins. Antwortet Postgres, dass die Relation nicht existiert, ist die Tabelle\nanders geschrieben, hat einen Großbuchstaben oder liegt in einem anderen Schema,\nund ",[90,363,365],{"href":364},"\u002Fblog\u002Fsupabase-relation-does-not-exist","dieser Fehler hat einen eigenen Beitrag",[10,367,368,369,372,373,378],{},"Eine Wiederherstellung aus der Ausgabe von ",[21,370,371],{},"pg_dump"," bringt dich nicht hierher.\n",[90,374,377],{"href":375,"rel":376},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fapp-pgdump.html",[94],"Seine Dokumentation","\nzählt auf, was ein Datendump enthält: \"Table data, large objects, and sequence\nvalues are dumped.\" Jeder Zähler kommt mit seinen Zeilen zurück.",[380,381],"diagram",{"alt":382,"caption":383,"src":384},"Eine Liste mit fünf nummerierten Zeilen, 1 bis 5, neben einem Zähler. Links zeigt der Zähler 1 und zeigt auf Zeile 1, die bernsteinfarben leuchtet, und endet an einer Plakette mit Key (id)=(1). Rechts zeigt der Zähler 6 und zeigt auf einen neuen Namen in der sechsten Zeile, mit einem Haken darunter.","Zeilen, die mit eigenen ids hineinkopiert wurden, lassen den Zähler, wo er war. setval schiebt ihn über die höchste id hinaus, und die nächste Zeile bekommt eine freie Nummer.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fthe-counter-is-behind-1600x640.png",[35,386,388],{"id":387},"die-drei-wege-mit-einem-duplikat-umzugehen","Die drei Wege, mit einem Duplikat umzugehen",[10,390,391],{},"Entscheide, was passieren soll, wenn der Wert schon da ist: die neue Zeile\nablehnen und das sagen, sie die alte ersetzen lassen oder sie gar nicht erst\nschicken.",[10,393,394,398],{},[395,396,397],"strong",{},"Lehn sie ab, mit eigenen Worten."," Das passt, wenn eine zweite Zeile ein Fehler\nwäre: eine Adresse, die schon registriert ist, ein Nutzername, der schon vergeben\nist. Das Insert scheitert, deine App liest den Code, und die Person sieht einen\nSatz, den du geschrieben hast:",[14,400,405],{"className":401,"code":403,"language":404,"meta":23},[402],"language-js","const { error } = await supabase.from('profiles').insert({ id: user.id, username })\n\nif (error?.code === '23505') {\n  setMessage('Dieser Nutzername ist vergeben. Versuch einen anderen.')\n} else if (error) {\n  setMessage('Etwas ist schiefgegangen. Bitte versuch es noch einmal.')\n}\n","js",[21,406,403],{"__ignoreMap":23},[10,408,409,412],{},[395,410,411],{},"Lass sie die alte ersetzen."," Das passt, wenn die neueste Fassung gewinnen soll:\neine Einstellungszeile pro Nutzer, ein Warenkorb pro Sitzung. Das ist ein Upsert,\nund der nächste Abschnitt zeigt, wann er schiefgeht.",[10,414,415,418],{},[395,416,417],{},"Schick sie nicht."," Manchmal sollte es die zweite Zeile nie geben, und sie kommt\naus einer dieser Quellen:",[420,421,422,426,429],"ul",{},[423,424,425],"li",{},"ein Knopf, der das Formular noch einmal schickt, wenn man ihn noch einmal\ndrückt. Deaktivier ihn, bis die erste Anfrage antwortet.",[423,427,428],{},"ein neuer Versuch nach einer langsamen Antwort, obwohl der erste durchgegangen\nwar",[423,430,431],{},"zwei Schreiber für eine Zeile, etwa ein Trigger, der für jeden neuen Nutzer ein\nProfil anlegt, und App-Code, der dasselbe Profil nach der Registrierung einfügt",[10,433,434,435,439,440,442],{},"Den Trigger-Fall solltest du auf Anhieb erkennen. Ein Trigger, der neue Nutzer in\neine eigene Tabelle kopiert, scheitert mit diesem Fehler, wenn der kopierte Wert\ndort schon steht, und Supabase meldet die ganze Registrierung als\n",[90,436,438],{"href":437},"\u002Fblog\u002Fdatabase-error-saving-new-user","\"Database error saving new user\"",". Den\n",[21,441,81],{}," selbst findest du in den Postgres-Logs, und dieser Beitrag zeigt, wo.",[35,444,446],{"id":445},"solltest-du-upsert-benutzen","Solltest du upsert benutzen?",[10,448,449],{},"Benutz es, wenn die neue Zeile die alte ersetzen soll, und prüf drei Dinge, bevor\ndu dich darauf verlässt.",[10,451,452,455,456,461,462,465,466,469,470,473,474,477],{},[21,453,454],{},".upsert()"," in supabase-js ist ein Insert, das etwas anderes tut, wenn die Zeile\nexistiert. ",[90,457,460],{"href":458,"rel":459},"https:\u002F\u002Fgithub.com\u002Fsupabase\u002Fsupabase-js\u002Fblob\u002Fmaster\u002Fpackages\u002Fcore\u002Fpostgrest-js\u002Fsrc\u002FPostgrestQueryBuilder.ts",[94],"Sein Quellcode","\nsagt es in zwei Zeilen: \"If ",[21,463,464],{},"true",", duplicate rows are ignored. If ",[21,467,468],{},"false",",\nduplicate rows are merged with existing rows.\" Das ist die Option\n",[21,471,472],{},"ignoreDuplicates",", und Postgres führt beide Fassungen als ",[21,475,476],{},"insert ... on conflict","\naus.",[10,479,480,487,492,493,496,497,499,500,502,503,505,506,508,509,514],{},[395,481,482,483,486],{},"1. ",[21,484,485],{},"onConflict"," muss die Spalte aus deinem Fehler nennen.",[90,488,491],{"href":489,"rel":490},"https:\u002F\u002Fdocs.postgrest.org\u002Fen\u002Fstable\u002Freferences\u002Fapi\u002Ftables_views.html",[94],"Die Dokumentation von PostgREST","\nsagt: \"By default, upsert operates based on the primary key columns\". Ein Upsert\nvon ",[21,494,495],{},"{ email }"," in eine Tabelle mit dem Primärschlüssel ",[21,498,245],{}," sucht nach einer\nKollision bei ",[21,501,245],{},", findet keine, weil die id neu ist, fügt ein und trifft auf die\nRegel für ",[21,504,29],{},", mit demselben ",[21,507,81],{},". Die\n",[90,510,513],{"href":511,"rel":512},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-insert.html",[94],"INSERT-Referenz von Postgres","\nsagt, die Ausweichaktion greift, \"if an arbiter constraint or index specified by\nconflict_target is violated\". Eine Kollision in jeder anderen Spalte lehnt die\nZeile ab wie vorher.",[14,516,519],{"className":517,"code":518,"language":404,"meta":23},[402],"await supabase\n  .from('settings')\n  .upsert({ user_id: user.id, theme: 'dark' }, { onConflict: 'user_id' })\n",[21,520,518],{"__ignoreMap":23},[10,522,523,526,527,530,531,534,535,540],{},[395,524,525],{},"2. Diese Spalte braucht eine eigene Unique-Regel."," Ohne sie hat Postgres nichts\nzum Abgleichen und antwortet\n",[21,528,529],{},"there is no unique or exclusion constraint matching the ON CONFLICT specification",",\nCode ",[21,532,533],{},"42P10","\n(",[90,536,539],{"href":537,"rel":538},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Foptimizer\u002Futil\u002Fplancat.c",[94],"die Zeile, die das auslöst",").",[10,542,543,546,547,552,553,323],{},[395,544,545],{},"3. Aus dem Browser liest Row Level Security mehr von deinen Regeln."," Row Level\nSecurity sind die Regeln pro Zeile, die Supabase bei jeder Anfrage mit deinem\nöffentlichen Schlüssel prüft. Für einen Upsert sagt Postgres'\n",[90,548,551],{"href":549,"rel":550},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fsql-createpolicy.html",[94],"Referenz zu Policies",":\n\"SELECT permissions are required on the relation, and the rows proposed for\ninsertion are checked using the relation's SELECT policies.\" Existiert die Zeile\nund der Upsert führt zusammen, wird die vorhandene Zeile zusätzlich gegen die\nUpdate-Regeln der Tabelle geprüft. Eine Zeile, die eine der beiden Prüfungen nicht\nbesteht, beendet die Anfrage mit\n",[90,554,556],{"href":555},"\u002Fblog\u002Fnew-row-violates-row-level-security-policy","new row violates row-level security policy",[10,558,559],{},"Auf der Warteliste scheitert ein Upsert aus dem Browser also, bis die Tabelle eine\nLeseregel hat, die ein Besucher besteht. Eine Leseregel, die ein Fremder für jede\nbeliebige Adresse besteht, ist eine Regel, die Fremde deine Liste lesen lässt. An\nder Tür heißt eine Zeile überschreiben: Man bekommt die Liste vorher gezeigt. Bei\nso einer Tabelle lehn das Duplikat lieber ab, oder nimm die Funktion aus dem\nnächsten Abschnitt.",[380,561],{"alt":562,"caption":563,"src":564},"Zwei Anfragen an dieselbe Tabelle. Ein Insert passiert ein Tor, insert, und erreicht sie. Ein Upsert muss drei Tore nacheinander passieren, insert, select und update; das Tor select ist geschlossen und bernsteinfarben, also bleibt der Upsert dort stehen.","Aus dem Browser wird ein Upsert gegen die Lese- und Update-Regeln der Tabelle geprüft, nicht nur gegen die Insert-Regel. Auf einer Tabelle, in die Besucher nur eintragen dürfen, hält ihn die Leseregel auf.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fupsert-reads-three-rules-1600x660.png",[35,566,568],{"id":567},"ist-es-sicher-diesen-fehler-deinen-nutzern-zu-zeigen","Ist es sicher, diesen Fehler deinen Nutzern zu zeigen?",[10,570,571],{},"Nein. Zeig einen eigenen Satz und behalte den rohen Fehler in deinen Logs, aus\nzwei Gründen: Die Detailzeile kann den Wert zurückliefern, und der Fehler bestätigt,\ndass der Wert existiert, egal was du anzeigst.",[10,573,574,577,578,581,582,589,590,323],{},[395,575,576],{},"Die Detailzeile wiederholt den Wert."," ",[21,579,580],{},"Key (email)=(maria@example.com)"," ist auf\nMarias eigenem Bildschirm harmlos und etwas ganz anderes in einem Screenshot in\neinem Supportforum, in einem Error-Tracker oder auf dem Bildschirm von jemandem,\nder die Adresse einer anderen Person in dein Formular getippt hat. Postgres\nschützt sie schon. Die Funktion, die die Zeile baut\n(",[90,583,586],{"href":584,"rel":585},"https:\u002F\u002Fgithub.com\u002Fpostgres\u002Fpostgres\u002Fblob\u002Fmaster\u002Fsrc\u002Fbackend\u002Faccess\u002Findex\u002Fgenam.c",[94],[21,587,588],{},"BuildIndexValueDescription","),\nprüft zuerst: \"if RLS is enabled for the relation. If so, return NULL to avoid\nleaking data.\" Eine Anfrage mit deinem öffentlichen Schlüssel an eine Tabelle mit\neingeschalteter Row Level Security bekommt ",[21,591,592],{},"\"details\": null",[10,594,595,596,599,600,602,603,607],{},"Andersherum gelesen verrät dir die Zeile etwas über die Tabelle. Zeigt deine\nBrowserkonsole ",[21,597,598],{},"Key (...)=(...)"," für eine Anfrage, die deine App mit dem\nöffentlichen Schlüssel gestellt hat, ist Row Level Security auf dieser Tabelle aus.\nDie eine Ausnahme ist eine Anfrage, die deine App über Servercode mit dem geheimen\nSchlüssel schickt, der die Regeln komplett überspringt. ",[21,601,137],{}," beweist weniger:\nPostgres lässt die Zeile auch weg, wenn der Aufrufer nicht jede Spalte im\nSchlüssel lesen darf.\n",[90,604,606],{"href":605},"\u002Fblog\u002Fenable-rls-on-every-supabase-table","Row Level Security für jede Tabelle einschalten","\nzeigt den Schalter und die erste Regel.",[10,609,610,611,615],{},"Eine Tabelle mit ausgeschalteten Regeln antwortet einem Fremden genauso bereitwillig\nwie dir. Unser ",[90,612,614],{"href":613},"\u002Fsecurity-scanner","kostenloser Scan"," liest deine Live-App mit\ndemselben öffentlichen Schlüssel, den ein Besucher bekommt, meldet, welche Tabellen\nZeilen herausgeben, und braucht kein Konto.",[10,617,618,621,622,625,626,631],{},[395,619,620],{},"Der Fehler beantwortet eine Frage, die du vielleicht nicht beantwortet haben\nwillst."," Auch ohne Detailzeile sagt ein ",[21,623,624],{},"409"," dem Absender, dass der Wert schon\nda ist. Postgres'\n",[90,627,630],{"href":628,"rel":629},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002Fcurrent\u002Fddl-rowsecurity.html",[94],"Seite über Row Security","\nsagt, dass Unique-Regeln \"always bypass row security to ensure that data integrity\nis maintained\", und bittet dich, Tabellen und Regeln so zu entwerfen, dass diese\nPrüfungen nicht zu einem verdeckten Kanal werden. Auf der Warteliste kann jeder\nherausfinden, ob eine Adresse auf deiner Liste steht, indem er sie einzutragen\nversucht. Der Türsteher liest den Namen nie vor, und wenn er jemanden abweist,\nweiß die Schlange trotzdem, dass er dastand.",[10,633,634],{},"Wenn das für deine Liste zählt, gib dem Browser in beiden Fällen dieselbe Antwort.\nEine Funktion in der Datenbank kann die Adresse eintragen, sie überspringen, wenn\nsie schon da ist, und in beiden Fällen nichts zurückgeben:",[14,636,639],{"className":637,"code":638,"language":275,"meta":23},[273],"create function public.join_waitlist(new_email text)\nreturns void\nlanguage sql\nsecurity definer\nset search_path = ''\nas $$\n  insert into public.waitlist (email)\n  values (new_email)\n  on conflict (email) do nothing;\n$$;\n",[21,640,638],{"__ignoreMap":23},[10,642,643,644,647,648,651,652,658,659,663],{},"Deine App ruft ",[21,645,646],{},"supabase.rpc('join_waitlist', { new_email: email })"," auf.\n",[21,649,650],{},"security definer"," führt das Insert als Eigentümer der Funktion aus, also brauchen\nBesucher überhaupt keine Regel auf der Tabelle, und die Regel, die sie eintragen\nließ, kann weg, sonst bleibt der alte Weg neben dem neuen offen.\n",[90,653,654,655,657],{"href":437},"Was \"Database error saving new user\" über ",[21,656,650],{}," sagt","\nerklärt, was es abgibt, und\n",[90,660,662],{"href":661},"\u002Fblog\u002Fsupabase-function-not-found","eine Supabase-Funktion aufrufen"," zeigt, wie die\nArgumentnamen passen müssen.",[380,665],{"alt":666,"caption":667,"src":668},"Dasselbe Duplikat an zwei Tabellen geschickt. Die linke Tabelle trägt ein Vorhängeschloss, und ihre Antwort lautet 23505 und 409 mit leerer details-Zeile. Die rechte Tabelle hat keines, und ihre Antwort trägt eine bernsteinfarbene details-Zeile mit Key (email)=(…).","Postgres schreibt den Wert nur für einen Aufrufer in die Detailzeile, für den Row Level Security nicht gilt. Siehst du ihn im Browser, sind die Regeln der Tabelle aus.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fthe-detail-line-and-rls-1600x700.png",[35,670,672],{"id":671},"bevor-du-änderst-wie-deine-app-schreibt","Bevor du änderst, wie deine App schreibt",[10,674,675],{},"Behalte eine Kopie der Datenbank von vor dem Moment, in dem du ein Insert auf ein\nUpsert umstellst oder einen Zähler zurücksetzt, und erfahre es, wenn eine Tabelle\nanfängt, Fremden zu antworten.",[10,677,678],{},[395,679,680,684],{},[90,681,683],{"href":682},"\u002Fsupabase-backups","Reeve Care"," bewahrt eine Kopie deiner Supabase-Datenbank auf:",[420,686,687,690,693,696,699,702,705],{},[423,688,689],{},"eine verschlüsselte Kopie nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos",[423,691,692],{},"jede Tabelle mit ihren Zeilen, wie sie waren, bevor ein Zusammenführen sie überschrieben hat",[423,694,695],{},"der ID-Zähler jeder Tabelle kommt bei einer Wiederherstellung mit ihren Zeilen zurück, sodass die nächste neue Zeile eine freie Nummer bekommt",[423,697,698],{},"jede Kopie wird geprüft, bevor sie zählt, indem die Zeilen in jeder Tabelle gezählt werden",[423,700,701],{},"auch deine hochgeladenen Dateien, sobald du eine Storage-Zugangsberechtigung verbindest",[423,703,704],{},"Wiederherstellung mit einem Klick, vorher wird eine Sicherheitskopie des aktuellen Stands angelegt",[423,706,707],{},"alles, was Monitor macht",[10,709,710],{},[395,711,712,716],{},[90,713,715],{"href":714},"\u002Fwebsite-monitoring","Reeve Monitor"," prüft weiter von außen:",[420,718,719,722,725,732],{},[423,720,721],{},"ob deine App erreichbar ist, jede Minute geprüft",[423,723,724],{},"der vollständige Sicherheitscheck jede Stunde, für bis zu drei Apps",[423,726,727,728,731],{},"eine Nachricht, wenn sich ein Ergebnis ",[395,729,730],{},"ändert",", sodass eine Tabelle, die nach einem Deploy Fremden antwortet, dich erreicht, ohne dass du nachsehen musst",[423,733,734],{},"ein monatlicher Bericht in klarer Sprache über das, was er gesehen hat",[35,736,738],{"id":737},"was-du-jetzt-tun-solltest","Was du jetzt tun solltest",[740,741,742],"key-takeaways",{},[420,743,744,753,762,768,774],{},[423,745,746,747,749,750,752],{},"Lies den Namen in Anführungszeichen. Tabelle und Spalte stehen vor ",[21,748,215],{},", und ",[21,751,239],{}," heißt Primärschlüssel.",[423,754,755,756,758,759,761],{},"Endet der Name auf ",[21,757,239],{}," und deine App schickt keine id, rück den Zähler mit der ",[21,760,322],{},"-Anweisung oben zurecht.",[423,763,764,765,767],{},"Fang ",[21,766,81],{}," in deiner App ab und zeig einen Satz, den du geschrieben hast, nie die rohe Meldung.",[423,769,770,771,773],{},"Benutz upsert nur dort, wo die neue Zeile die alte ersetzen soll, mit ",[21,772,485],{}," auf der Spalte aus dem Fehler.",[423,775,776,777,779],{},"Zeigt die Browserkonsole eine Zeile ",[21,778,598],{}," für eine Anfrage mit deinem öffentlichen Schlüssel, schalte Row Level Security für diese Tabelle ein.",[10,781,782,783,785,786,789],{},"Fang beim Namen in Anführungszeichen an, denn er sagt, welche Regel die Zeile\nabgelehnt hat und welche Spalte sie bewacht. Bevor du ein ",[21,784,322],{},", ein Upsert oder\nirgendetwas anderes ausführst, das Zeilen umschreibt, auf die sich Leute verlassen,\n",[90,787,788],{"href":682},"behalte eine Kopie der Datenbank"," von vor der Änderung.",{"title":23,"searchDepth":791,"depth":791,"links":792},3,[793,795,796,797,798,799,800,801],{"id":37,"depth":794,"text":38},2,{"id":182,"depth":794,"text":183},{"id":294,"depth":794,"text":295},{"id":387,"depth":794,"text":388},{"id":445,"depth":794,"text":446},{"id":567,"depth":794,"text":568},{"id":671,"depth":794,"text":672},{"id":737,"depth":794,"text":738},"Sicherheitsgrundlagen","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcover-1200x630.png","Eine Namensliste auf einem Klemmbrett, eine Zeile in Türkis, davor ein Zettel mit demselben Namen in Bernstein, der draußen bleibt.","Supabase \"duplicate key value violates unique constraint\" heißt: Der Wert ist schon vergeben. Spalte finden, Lösung wählen, Wert vom Bildschirm fernhalten.",false,"md",[809,812,814,817,820],{"q":810,"a":811},"Was bedeutet duplicate key value violates unique constraint?","Postgres hat sich geweigert, eine Zeile anzulegen oder zu ändern, weil eine andere Zeile denselben Wert schon in einer Spalte oder Spaltengruppe hat, die jeden Wert nur einmal erlaubt. Geschrieben wurde nichts. Der Code ist 23505, und aus einer Supabase-App kommt er als HTTP 409.",{"q":183,"a":813},"Das sagt der Name der Regel in Anführungszeichen. Postgres benennt eine Unique-Regel nach Tabelle und Spalten mit _key am Ende, waitlist_email_key ist also die Spalte email der Tabelle waitlist, und ein Name auf _pkey ist der Primärschlüssel, meist id. Die Detailzeile nennt, wenn sie da ist, Spalte und Wert.",{"q":815,"a":816},"Soll ich upsert benutzen?","Nur wenn die neue Zeile die alte ersetzen soll, etwa eine Einstellungszeile pro Nutzer. Setz onConflict auf die Spalte aus dem Fehler, denn Supabase-Upserts prüfen standardmäßig den Primärschlüssel. Aus dem Browser braucht ein Upsert außerdem Leseregeln, die die Zeile besteht. Auf einer Tabelle, in die Besucher nur eintragen dürfen, scheitert er also, oder du musst die Tabelle zum Lesen öffnen.",{"q":818,"a":819},"Warum passiert das bei der Registrierung?","Zwei Wege führen dorthin: Ein Trigger kopiert jeden neuen Nutzer in eine eigene Tabelle, und der kopierte Wert steht dort schon, oder deine App legt dieselbe Profilzeile an, die der Trigger schon angelegt hat. Supabase meldet beides als \"Database error saving new user\", und den 23505 selbst findest du in den Postgres-Logs.",{"q":821,"a":822},"Ist es sicher, diesen Fehler Nutzern zu zeigen?","Nein. Die Detailzeile kann den Wert wiederholen, die Meldung nennt Tabelle und Spalte, und der Fehler selbst verrät dem Absender, dass der Wert existiert. Fang den Code 23505 ab, zeig einen Satz, den du geschrieben hast, und behalte den rohen Fehler in deinen eigenen Logs.","\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint\u002Fcard-800x500.png",[825,826,827,828,829,830,831,832],"duplicate key value violates unique constraint","supabase duplicate key fehler","postgres 23505","supabase upsert duplikat","key already exists","duplicate key value violates unique constraint pkey","there is no unique or exclusion constraint matching the on conflict specification","supabase onconflict",{},true,"\u002Fblog\u002Fduplicate-key-value-violates-unique-constraint","2026-10-24",{"title":5,"description":805},"blog\u002Fduplicate-key-value-violates-unique-constraint",[840,841,842],"Supabase \"duplicate key value violates unique constraint\" (Postgres-Code 23505, HTTP 409 in deiner App) heißt: Eine Zeile hat diesen Wert schon in einer Spalte, die jeden Wert nur einmal erlaubt. Der Name in Anführungszeichen sagt, welche Spalte.","\"Key (id)=(1) already exists\", obwohl deine App keine id schickt, heißt: Der ID-Zähler der Tabelle hinkt ihren Zeilen hinterher. Eine setval-Zeile bringt ihn in Ordnung.","Upsert hilft nur, wenn onConflict genau diese Spalte nennt, und aus dem Browser braucht es auch die Leseregeln der Tabelle. Zeig deinen Nutzern einen eigenen Satz: Die rohe Meldung kann den Wert zurückliefern.","jKKQT_XkxP1kwibMZAwPbEoV7sTcKwFZlEyi3E5WvU4",[845,851,857,863,869,875,881,887,893,899,905,912,918,924,930,936,942,943,948,953,959,965,971,977,983,989,995,1000,1006,1012,1018,1024,1030,1036,1042,1048,1054,1060,1066,1072,1078,1084,1090,1096,1102,1108,1113,1119,1125,1131,1137,1143,1149,1155,1161,1167,1173,1179,1185,1191,1197,1203,1208,1214,1220,1226,1232,1238,1244,1250,1256,1262,1268,1274,1280,1286,1291,1297,1303,1309,1315,1321,1327,1333,1338,1344,1350,1356,1361,1367],{"path":846,"title":847,"description":848,"published":849,"category":802,"image":850,"draft":806},"\u002Fblog\u002Fmax-client-connections-reached","Supabase \"max client connections reached\" erklärt","Supabase \"max client connections reached\": Der Pooler hat keinen Platz für noch einen Client. Der Port in deinem Connection String setzt das Limit.","2026-11-09","\u002Fblog\u002Fmax-client-connections-reached\u002Fcard-800x500.png",{"path":852,"title":853,"description":854,"published":855,"category":802,"image":856,"draft":806},"\u002Fblog\u002Funsupported-provider-provider-is-not-enabled","Supabase-Fehler: \"Unsupported provider: provider is not enabled\"","\"Unsupported provider: provider is not enabled\" heißt: Google-Login ist in deinem Supabase-Projekt aus. Die Lösung, und was bei redirect_uri_mismatch hilft.","2026-11-08","\u002Fblog\u002Funsupported-provider-provider-is-not-enabled\u002Fcard-800x500.png",{"path":858,"title":859,"description":860,"published":861,"category":802,"image":862,"draft":806},"\u002Fblog\u002Fviolates-foreign-key-constraint","Supabase-Fehler \"violates foreign key constraint\" beim Einfügen","Supabase \"insert or update on table violates foreign key constraint\": Deine Zeile zeigt auf eine, die es nicht gibt. Welche, warum, und die Lösung.","2026-11-07","\u002Fblog\u002Fviolates-foreign-key-constraint\u002Fcard-800x500.png",{"path":864,"title":865,"description":866,"published":867,"category":802,"image":868,"draft":806},"\u002Fblog\u002Fnull-value-violates-not-null-constraint","Supabase-Fehler \"null value in column violates not-null constraint\"","Supabase \"null value in column violates not-null constraint\": Eine Pflichtspalte kam leer an. Woher die Null kommt, und die Lösung.","2026-11-06","\u002Fblog\u002Fnull-value-violates-not-null-constraint\u002Fcard-800x500.png",{"path":870,"title":871,"description":872,"published":873,"category":802,"image":874,"draft":806},"\u002Fblog\u002Fauth-session-missing","Supabase \"Auth session missing!\": wo die Sitzung geblieben ist","\"Auth session missing!\" heißt: supabase-js fand keine angemeldete Sitzung und fragte Supabase gar nicht erst. Wo die Sitzung blieb, und die Lösung je Fall.","2026-11-05","\u002Fblog\u002Fauth-session-missing\u002Fcard-800x500.png",{"path":876,"title":877,"description":878,"published":879,"category":802,"image":880,"draft":806},"\u002Fblog\u002Finvalid-claim-missing-sub-claim","Supabase \"invalid claim: missing sub claim\": niemand ist angemeldet","\"invalid claim: missing sub claim\" heißt: Supabase Auth bekam ein echtes Token ohne Nutzer darin, deinen Projektschlüssel statt einer Sitzung.","2026-11-04","\u002Fblog\u002Finvalid-claim-missing-sub-claim\u002Fcard-800x500.png",{"path":882,"title":883,"description":884,"published":885,"category":802,"image":886,"draft":806},"\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function","\"Failed to send a request to the Edge Function\": der echte Grund","\"Failed to send a request to the Edge Function\" heißt: Der Browser hat keine lesbare Antwort bekommen. Seine Konsole sagt, warum, und ob deine Funktion lief.","2026-11-03","\u002Fblog\u002Ffailed-to-send-a-request-to-the-edge-function\u002Fcard-800x500.png",{"path":888,"title":889,"description":890,"published":891,"category":802,"image":892,"draft":806},"\u002Fblog\u002Fsupabase-auth-redirects-to-localhost","Warum Supabase-E-Mail-Links auf localhost:3000 weiterleiten","Leitet Supabase auf localhost:3000 um, ist die Site URL unverändert, oder deine Adresse fehlt in Redirect URLs. Die Lösung und der Platzhalter, den du meidest.","2026-11-02","\u002Fblog\u002Fsupabase-auth-redirects-to-localhost\u002Fcard-800x500.png",{"path":894,"title":895,"description":896,"published":897,"category":802,"image":898,"draft":806},"\u002Fblog\u002Femail-link-is-invalid-or-has-expired","Supabase \"Email link is invalid or has expired\" bei einem neuen Link","Bei einem Link von vor einer Minute heißt Supabases \"Email link is invalid or has expired\" meist: Ein Mailfilter hat ihn zuerst benutzt. Prüfen und beheben.","2026-11-01","\u002Fblog\u002Femail-link-is-invalid-or-has-expired\u002Fcard-800x500.png",{"path":900,"title":901,"description":902,"published":903,"category":802,"image":904,"draft":806},"\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout","Supabase \"canceling statement due to statement timeout\" erklärt","Supabase \"canceling statement due to statement timeout\": Eine Abfrage lief länger als ihre Rolle darf, 3 Sekunden für Besucher. Prüf zuerst die RLS-Regel.","2026-10-31","\u002Fblog\u002Fcanceling-statement-due-to-statement-timeout\u002Fcard-800x500.png",{"path":906,"title":907,"description":908,"published":909,"category":910,"image":911,"draft":806},"\u002Fblog\u002Fdatabase-error-deleting-user","\"Database error deleting user\" in Supabase und was mitgelöscht wird","\"Database error deleting user\" in Supabase heißt: Deine Datenbank hat das Löschen abgelehnt. Der Grund steht im Log, die Lösung entscheidet, was mitgeht.","2026-10-30","Backups","\u002Fblog\u002Fdatabase-error-deleting-user\u002Fcard-800x500.png",{"path":913,"title":914,"description":915,"published":916,"category":802,"image":917,"draft":806},"\u002Fblog\u002Fstripe-webhook-no-signatures-found","\"No signatures found matching the expected signature\" bei Supabase","Ein Stripe-Webhook in einer Supabase Edge Function meldet \"No signatures found matching the expected signature\"? Zwei Ursachen. Prüfe weiter.","2026-10-29","\u002Fblog\u002Fstripe-webhook-no-signatures-found\u002Fcard-800x500.png",{"path":919,"title":920,"description":921,"published":922,"category":802,"image":923,"draft":806},"\u002Fblog\u002Fsupabase-invalid-login","\"Invalid login credentials\" in Supabase: die drei Ursachen","Supabase sagt \"Invalid login credentials\" bei falschem Passwort, unbekannter E-Mail und Google-Konto. So erkennst du, welcher Fall es ist.","2026-10-28","\u002Fblog\u002Fsupabase-invalid-login\u002Fcard-800x500.png",{"path":925,"title":926,"description":927,"published":928,"category":802,"image":929,"draft":806},"\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code","\"Edge Function returned a non-2xx status code\": der echte Fehler","\"Edge Function returned a non-2xx status code\" heißt: Deine Supabase-Funktion hat mit einem Fehler geantwortet. So liest du den echten.","2026-10-27","\u002Fblog\u002Fedge-function-returned-a-non-2xx-status-code\u002Fcard-800x500.png",{"path":931,"title":932,"description":933,"published":934,"category":802,"image":935,"draft":806},"\u002Fblog\u002Fcertificate-not-trusted","Zertifikat nicht vertrauenswürdig: drei Ursachen außer Ablauf","Zertifikat nicht vertrauenswürdig, obwohl das Datum stimmt? Es nennt eine andere Adresse, hat sich selbst signiert oder seiner Kette fehlt ein Glied.","2026-10-26","\u002Fblog\u002Fcertificate-not-trusted\u002Fcard-800x500.png",{"path":937,"title":938,"description":939,"published":940,"category":802,"image":941,"draft":806},"\u002Fblog\u002Finvalid-input-syntax-for-type-uuid","Supabase \"invalid input syntax for type uuid\": was im Zitat steht","Supabase \"invalid input syntax for type uuid\" heißt: Deine App hat etwas geschickt, das keine ID ist. Zitat lesen, Abfrage zurückhalten, Spalte behalten.","2026-10-25","\u002Fblog\u002Finvalid-input-syntax-for-type-uuid\u002Fcard-800x500.png",{"path":835,"title":5,"description":805,"published":836,"category":802,"image":823,"draft":806},{"path":364,"title":944,"description":945,"published":946,"category":802,"image":947,"draft":806},"Supabase \"relation does not exist\", obwohl die Tabelle da ist","Supabase \"relation does not exist\" oder PGRST205: Keine Tabelle passt zu Name und Schema deiner Abfrage. Vier Prüfungen, und was eine Freigabe öffnet.","2026-10-23","\u002Fblog\u002Fsupabase-relation-does-not-exist\u002Fcard-800x500.png",{"path":661,"title":949,"description":950,"published":951,"category":802,"image":952,"draft":806},"Supabase PGRST202: \"Could not find the function in the schema cache\"","Supabase \"Could not find the function in the schema cache\" nennt die Argumente, die deine App geschickt hat. Vergleich sie mit der Funktion.","2026-10-22","\u002Fblog\u002Fsupabase-function-not-found\u002Fcard-800x500.png",{"path":954,"title":955,"description":956,"published":957,"category":802,"image":958,"draft":806},"\u002Fblog\u002Fsupabase-schema-cache-column-error","Supabase PGRST204: \"Could not find the column in the schema cache\"","Supabase \"Could not find the column in the schema cache\" heißt meist: Der Name passt nicht. Vier Prüfungen und der Einzeiler zum Neuladen.","2026-10-21","\u002Fblog\u002Fsupabase-schema-cache-column-error\u002Fcard-800x500.png",{"path":960,"title":961,"description":962,"published":963,"category":802,"image":964,"draft":806},"\u002Fblog\u002Fsupabase-email-rate-limit-exceeded","Supabase \"Email Rate Limit Exceeded\": zwei Limits, ein Fehlercode","Supabase \"Email Rate Limit Exceeded\" begrenzt jede E-Mail deines Projekts: 2 pro Stunde beim eingebauten Absender. Was zählt und wie du es anhebst.","2026-10-20","\u002Fblog\u002Fsupabase-email-rate-limit-exceeded\u002Fcard-800x500.png",{"path":966,"title":967,"description":968,"published":969,"category":802,"image":970,"draft":806},"\u002Fblog\u002Fsupabase-users-logged-out","Supabase \"Invalid Refresh Token: Refresh Token Not Found\": 3 Ursachen","Supabase \"Invalid Refresh Token: Refresh Token Not Found\" meldet deine Nutzer ab. Drei Ursachen, und wie das Auth-Log sie trennt.","2026-10-19","\u002Fblog\u002Fsupabase-users-logged-out\u002Fcard-800x500.png",{"path":972,"title":973,"description":974,"published":975,"category":802,"image":976,"draft":806},"\u002Fblog\u002Fpermission-denied-for-schema-public","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.","2026-10-18","\u002Fblog\u002Fpermission-denied-for-schema-public\u002Fcard-800x500.png",{"path":978,"title":979,"description":980,"published":981,"category":802,"image":982,"draft":806},"\u002Fblog\u002Fsupabase-bucket-not-found","Supabase \"Bucket not found\": die drei Dinge, die es bedeutet","Supabase \"Bucket not found\" ist eins von drei Dingen: ein öffentlicher Link zu einem privaten Bucket, ein ungenauer Name oder das falsche Projekt.","2026-10-17","\u002Fblog\u002Fsupabase-bucket-not-found\u002Fcard-800x500.png",{"path":984,"title":985,"description":986,"published":987,"category":802,"image":988,"draft":806},"\u002Fblog\u002Fsupabase-invalid-jwt","Supabase invalid JWT: Welcher Schlüssel streikt hier?","Ein Supabase invalid JWT ist PGRST301: ein signiertes Token hat die Prüfung nicht bestanden. In neueren Projekten ist das die Sitzung, nicht der API-Schlüssel.","2026-10-16","\u002Fblog\u002Fsupabase-invalid-jwt\u002Fcard-800x500.png",{"path":990,"title":991,"description":992,"published":993,"category":802,"image":994,"draft":806},"\u002Fblog\u002Fsupabase-single-row-error","Supabase \"multiple (or no) rows returned\" heißt meist: keine Zeilen","Supabase gibt PGRST116 zurück, \"JSON object requested, multiple (or no) rows returned\", wenn die Abfrage nichts fand. Versteckte Zeilen wirken fehlend.","2026-10-15","\u002Fblog\u002Fsupabase-single-row-error\u002Fcard-800x500.png",{"path":437,"title":996,"description":997,"published":998,"category":802,"image":999,"draft":806},"\"Database error saving new user\" in Supabase: wo du nachsiehst","\"Database error saving new user\" heißt: etwas an deiner Supabase-Registrierung ist fehlgeschlagen. Der echte Fehler steht in deinen Logs.","2026-10-14","\u002Fblog\u002Fdatabase-error-saving-new-user\u002Fcard-800x500.png",{"path":1001,"title":1002,"description":1003,"published":1004,"category":802,"image":1005,"draft":806},"\u002Fblog\u002Fwhat-is-supabase","Was ist Supabase? Die Datenbank, die dein Builder ausgesucht hat","Was ist Supabase: die Datenbank, der Login und der Dateispeicher hinter deiner App. Supabase betreibt die Server, die Regeln zu deinen Zeilen gehören dir.","2026-10-13","\u002Fblog\u002Fwhat-is-supabase\u002Fcard-800x500.png",{"path":1007,"title":1008,"description":1009,"published":1010,"category":802,"image":1011,"draft":806},"\u002Fblog\u002Faws-access-key-exposed-in-frontend","Ein offengelegter AWS-Schlüssel im Frontend ist kein OpenAI-Key","Ein im Frontend offengelegter AWS-Zugriffsschlüssel liest und schreibt deinen Speicher. Die Lösung ist meist eine presigned URL, kein neuer Schlüssel.","2026-10-12","\u002Fblog\u002Faws-access-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1013,"title":1014,"description":1015,"published":1016,"category":802,"image":1017,"draft":806},"\u002Fblog\u002Fstorage-upload-violates-row-level-security","\"Row-level security policy for table objects\" beim Upload","\"New row violates row-level security policy for table objects\" heißt: Deinem Upload fehlt eine insert-Regel. Ein öffentlicher Bucket fügt keine hinzu.","2026-10-11","\u002Fblog\u002Fstorage-upload-violates-row-level-security\u002Fcard-800x500.png",{"path":1019,"title":1020,"description":1021,"published":1022,"category":802,"image":1023,"draft":806},"\u002Fblog\u002Fbase44-security-scan","Base44 Security Scan: das, was nur er sehen kann","Der Base44 Security Scan prüft sieben Arten von Problemen von innen. Hier ist die Hälfte, die er liest und sonst niemand, und die Hälfte, die er nie ansieht.","2026-10-10","\u002Fblog\u002Fbase44-security-scan\u002Fcard-800x500.png",{"path":1025,"title":1026,"description":1027,"published":1028,"category":802,"image":1029,"draft":806},"\u002Fblog\u002Fapi-key-leaked-what-to-do","Dein API-Key wurde geleakt. Die richtige Reihenfolge","Ein API-Key wurde geleakt und du weißt nicht, was zuerst zu tun ist. Nicht jeder Key im Frontend ist ein Leck, und die Reihenfolge zählt mehr als das Tempo.","2026-10-09","\u002Fblog\u002Fapi-key-leaked-what-to-do\u002Fcard-800x500.png",{"path":1031,"title":1032,"description":1033,"published":1034,"category":802,"image":1035,"draft":806},"\u002Fblog\u002Finfinite-recursion-in-policy-for-relation","\"Infinite recursion detected in policy\" ohne RLS abzuschalten","\"Infinite recursion detected in policy for relation\" heißt: Deine Policy hat die Tabelle gefragt, die sie schützt. So brichst du den Kreis.","2026-10-08","\u002Fblog\u002Finfinite-recursion-in-policy-for-relation\u002Fcard-800x500.png",{"path":1037,"title":1038,"description":1039,"published":1040,"category":802,"image":1041,"draft":806},"\u002Fblog\u002Fno-api-key-found-in-request","\"No API key found in request\" in Supabase, und die falsche Lösung","\"No API key found in request\" heißt, deine Supabase-Anfrage kam ohne Schlüssel an. Die meisten Antworten zeigen stattdessen auf deine Datenbankregeln.","2026-10-07","\u002Fblog\u002Fno-api-key-found-in-request\u002Fcard-800x500.png",{"path":1043,"title":1044,"description":1045,"published":1046,"category":802,"image":1047,"draft":806},"\u002Fblog\u002Fis-base44-safe","Ist Base44 sicher? Was 5.442 echte Base44-Apps zeigten","Ist Base44 sicher? Wir haben 5.442 laufende Base44-Apps mit neun Checks geprüft. Fast alle bekamen ein B, aus Gründen, die der Inhaber nie gewählt hat.","2026-10-06","\u002Fblog\u002Fis-base44-safe\u002Fcard-800x500.png",{"path":1049,"title":1050,"description":1051,"published":1052,"category":802,"image":1053,"draft":806},"\u002Fblog\u002Fmove-a-secret-to-an-edge-function","API-Schlüssel verstecken: ab in eine Supabase Edge Function","Einen API-Schlüssel verstecken heißt, ihn aus dem Browser zu nehmen. Eine Supabase Edge Function ist der kleinste Ort dafür, und zwei Schritte zählen mehr.","2026-10-05","\u002Fblog\u002Fmove-a-secret-to-an-edge-function\u002Fcard-800x500.png",{"path":1055,"title":1056,"description":1057,"published":1058,"category":802,"image":1059,"draft":806},"\u002Fblog\u002Fenv-file-exposed-on-your-server","Ist deine .env-Datei öffentlich? Zwölf Pfade zum Prüfen","Ist deine .env-Datei auf deinem eigenen Webserver öffentlich? Zwölf Adressen sagen es dir, und ein Treffer heißt: alles darin ist längst öffentlich.","2026-10-04","\u002Fblog\u002Fenv-file-exposed-on-your-server\u002Fcard-800x500.png",{"path":1061,"title":1062,"description":1063,"published":1064,"category":802,"image":1065,"draft":806},"\u002Fblog\u002Fis-v0-safe","v0-Sicherheit: alle 1.790 gescannten v0-Apps bekamen ein A","v0-Sicherheit, gemessen an 1.790 laufenden v0-Apps: Jede bekam ein A. Nur 17 nannten eine Datenbank, und genau das misst das A vor allem.","2026-10-03","\u002Fblog\u002Fis-v0-safe\u002Fcard-800x500.png",{"path":1067,"title":1068,"description":1069,"published":1070,"category":802,"image":1071,"draft":806},"\u002Fblog\u002Fis-bolt-safe","Ist Bolt sicher? Was 1.123 echte Bolt-Apps zeigten","Ist Bolt sicher? Wir haben 1.123 laufende Bolt-Apps mit neun Checks geprüft. Das Hosting war sauber. Die Funde waren Schlüssel und offene Tabellen in den Apps.","2026-10-02","\u002Fblog\u002Fis-bolt-safe\u002Fcard-800x500.png",{"path":1073,"title":1074,"description":1075,"published":1076,"category":802,"image":1077,"draft":806},"\u002Fblog\u002Fis-supabase-down-or-your-app","Ist Supabase down oder liegt es an deiner App? So erkennst du es","Ist Supabase down oder liegt es an deiner App? Ein pausiertes Projekt, eine volle Datenbank und ein eigener Fehler sehen gleich aus. Vier Prüfungen helfen.","2026-10-01","\u002Fblog\u002Fis-supabase-down-or-your-app\u002Fcard-800x500.png",{"path":1079,"title":1080,"description":1081,"published":1082,"category":802,"image":1083,"draft":806},"\u002Fblog\u002Fdoes-supabase-encrypt-my-data","Verschlüsselt Supabase meine Daten? Ja. Wovor das schützt","Verschlüsselt Supabase Daten? Ja: AES-256 im Ruhezustand, TLS bei der Übertragung, SOC 2 und ISO 27001. Was jedes abdeckt und welches Leck keines stoppt.","2026-09-30","\u002Fblog\u002Fdoes-supabase-encrypt-my-data\u002Fcard-800x500.png",{"path":1085,"title":1086,"description":1087,"published":1088,"category":802,"image":1089,"draft":806},"\u002Fblog\u002Fsupabase-free-plan-limits","Supabase Gratis-Tarif: die Limits und was an jedem passiert","Die Limits des Supabase-Gratis-Tarifs: 500 MB Datenbank, 5 GB Egress und keine Backups. Was an jeder Grenze passiert und was $25 für Pro ändern.","2026-09-29","\u002Fblog\u002Fsupabase-free-plan-limits\u002Fcard-800x500.png",{"path":1091,"title":1092,"description":1093,"published":1094,"category":910,"image":1095,"draft":806},"\u002Fblog\u002Ftest-your-supabase-backup","Teste dein Supabase-Backup, bevor du es brauchst","So testest du dein Supabase-Backup: in ein Übungsprojekt einspielen, Zeilen zählen, anmelden und die Zeile suchen, die einer abgeschnittenen Datei fehlt.","2026-09-28","\u002Fblog\u002Ftest-your-supabase-backup\u002Fcard-800x500.png",{"path":1097,"title":1098,"description":1099,"published":1100,"category":910,"image":1101,"draft":806},"\u002Fblog\u002Fsupabase-backup-github-action","Kostenloses Supabase-Backup per GitHub Action, mit Haken","Eine Supabase-Backup-GitHub-Action kostet nichts und passt für viele Apps. Der Workflow, der richtige Connection String und das Egress pro Lauf.","2026-09-27","\u002Fblog\u002Fsupabase-backup-github-action\u002Fcard-800x500.png",{"path":1103,"title":1104,"description":1105,"published":1106,"category":910,"image":1107,"draft":806},"\u002Fblog\u002Fdownload-your-supabase-backup","Warum du dein Supabase-Backup nicht herunterladen kannst","Dein Supabase-Backup kannst du auf aktuellen Projekten nicht herunterladen, weil die Tageskopie physisch ist. Wie du es prüfst und selbst eine Kopie hältst.","2026-09-26","\u002Fblog\u002Fdownload-your-supabase-backup\u002Fcard-800x500.png",{"path":1109,"title":1110,"description":1111,"published":1106,"category":802,"image":1112,"draft":806},"\u002Fblog\u002Fsupabase-permission-denied-for-table","Supabase \"permission denied for table\": der fehlende Grant","Ab 30. Oktober antwortet eine neue Supabase-Tabelle mit \"permission denied for table\", bis du Zugriff erteilst. Der Grant aus der E-Mail ist die halbe Lösung.","\u002Fblog\u002Fsupabase-permission-denied-for-table\u002Fcard-800x500.png",{"path":1114,"title":1115,"description":1116,"published":1117,"category":802,"image":1118,"draft":806},"\u002Fblog\u002Fopen-api-endpoint-exposed","Ist ein offener API-Endpunkt ein Sicherheitsproblem? Sieh ins JSON","Dein Scan hat einen offenen API-Endpunkt gemeldet. Ob das zählt, hängt davon ab, was zurückkam, und die meisten hier gehörten der Plattform.","2026-09-25","\u002Fblog\u002Fopen-api-endpoint-exposed\u002Fcard-800x500.png",{"path":1120,"title":1121,"description":1122,"published":1123,"category":802,"image":1124,"draft":806},"\u002Fblog\u002Flovable-security-scan","Lovable Security Scan: das Eine, was er nicht beweist","Lovable Security Scan: was Quick Scan und Deep Scan prüfen, wann welcher läuft, und das Eine, was kein Scan von innen beweisen kann.","2026-09-24","\u002Fblog\u002Flovable-security-scan\u002Fcard-800x500.png",{"path":1126,"title":1127,"description":1128,"published":1129,"category":910,"image":1130,"draft":806},"\u002Fblog\u002Fsupabase-backup-auth-users","Warum in deinem Supabase-Dump keine Nutzer stehen","Führe supabase db dump allein aus und du bekommst die Form deiner Datenbank und keine einzige Zeile, und das auth-Schema mit deinen Nutzern fehlt ganz.","2026-09-23","\u002Fblog\u002Fsupabase-backup-auth-users\u002Fcard-800x500.png",{"path":1132,"title":1133,"description":1134,"published":1135,"category":802,"image":1136,"draft":806},"\u002Fblog\u002Fdomain-and-certificate-expiry","Domain abgelaufen, Website offline: was jetzt passiert","Deine Domain ist abgelaufen und deine Website ist offline. Welche Frist jetzt läuft, warum das Zertifikat das kleinere Problem ist, und wie du beides prüfst.","2026-09-22","\u002Fblog\u002Fdomain-and-certificate-expiry\u002Fcard-800x500.png",{"path":1138,"title":1139,"description":1140,"published":1141,"category":802,"image":1142,"draft":806},"\u002Fblog\u002Fis-lovable-safe","Ist Lovable sicher? Was 18.554 echte Lovable-Apps zeigten","Ist Lovable sicher? Wir haben 18.554 laufende Lovable-Apps mit neun Checks geprüft. Die Plattform war die sauberste von fünf. Die Funde saßen in den Apps.","2026-09-21","\u002Fblog\u002Fis-lovable-safe\u002Fcard-800x500.png",{"path":1144,"title":1145,"description":1146,"published":1147,"category":802,"image":1148,"draft":806},"\u002Fblog\u002Fvibe-coded-app-security-checklist","Die Sicherheits-Checkliste für Vibe-Coding, in neun Prüfungen","Eine Sicherheits-Checkliste für Vibe-Coding mit neun Punkten, die jeder von außen an deiner Live-App nachprüfen kann, jeder mit einem Test in einer Zeile.","2026-09-20","\u002Fblog\u002Fvibe-coded-app-security-checklist\u002Fcard-800x500.png",{"path":1150,"title":1151,"description":1152,"published":1153,"category":802,"image":1154,"draft":806},"\u002Fblog\u002Fstripe-secret-key-in-frontend","Ein geheimer Stripe-Schlüssel im Frontend kann Geld bewegen","Ein geheimer Stripe-Schlüssel im Frontend kann erstatten, abbuchen und jeden Kundendatensatz lesen, den du hast. Dein pk_live_-Schlüssel gehört dorthin.","2026-09-19","\u002Fblog\u002Fstripe-secret-key-in-frontend\u002Fcard-800x500.png",{"path":1156,"title":1157,"description":1158,"published":1159,"category":802,"image":1160,"draft":806},"\u002Fblog\u002Fvite-and-next-public-env-vars","Vite-Env-Variablen offengelegt: der Präfix heißt veröffentlichen","Vite-Env-Variablen offengelegt: Deine App hat getan, was der Präfix verlangt. VITE_ und NEXT_PUBLIC_ heißen veröffentlichen, und die KI kannte den Preis nie.","2026-09-18","\u002Fblog\u002Fvite-and-next-public-env-vars\u002Fcard-800x500.png",{"path":1162,"title":1163,"description":1164,"published":1165,"category":802,"image":1166,"draft":806},"\u002Fblog\u002Fbase44-source-maps","Base44-Sicherheit: was ein Scan meldet und was du ändern kannst","Base44-Sicherheit auf 5.438 gescannten Apps: Drei Befunde auf fast jeder gehören der Plattform, die Source Map ist das Base44-Badge, und deine Liste ist kurz.","2026-09-17","\u002Fblog\u002Fbase44-source-maps\u002Fcard-800x500.png",{"path":1168,"title":1169,"description":1170,"published":1171,"category":802,"image":1172,"draft":806},"\u002Fblog\u002Fis-cursor-ai-safe","Ist Cursor AI sicher? Editor, Code und die App, die du veröffentlichst","Ist Cursor AI sicher? Drei Fragen in einer Suche: was Cursor behält, was sein Code falsch macht und ob deine veröffentlichte App offen steht.","2026-09-16","\u002Fblog\u002Fis-cursor-ai-safe\u002Fcard-800x500.png",{"path":1174,"title":1175,"description":1176,"published":1177,"category":802,"image":1178,"draft":806},"\u002Fblog\u002Fis-replit-safe","Ist Replit sicher? Was wir in 3.042 Replit-Apps gefunden haben","Ist Replit sicher? Wir haben 3.042 laufende Replit-Apps mit neun externen Checks geprüft. Die Funde saßen in der veröffentlichten App, nicht im Hosting.","2026-09-15","\u002Fblog\u002Fis-replit-safe\u002Fcard-800x500.png",{"path":1180,"title":1181,"description":1182,"published":1183,"category":910,"image":1184,"draft":806},"\u002Fblog\u002Fsupabase-storage-backup","Supabase Storage Backup: deine Datenbankkopie hat keine Dateien","Ein Supabase Storage Backup ist eine eigene Aufgabe. Datenbank-Backups enthalten die Liste deiner Dateien und keine davon, also bleibt jeder Upload kaputt.","2026-09-14","\u002Fblog\u002Fsupabase-storage-backup\u002Fcard-800x500.png",{"path":1186,"title":1187,"description":1188,"published":1189,"category":910,"image":1190,"draft":806},"\u002Fblog\u002Fsupabase-point-in-time-recovery","Supabase Point-in-Time Recovery: Kosten und Grenzen","Supabase Point-in-Time Recovery spult deine Datenbank auf jede Sekunde der letzten Woche zurück. $100 im Monat zusätzlich zu Pro, und nur die Datenbank.","2026-09-13","\u002Fblog\u002Fsupabase-point-in-time-recovery\u002Fcard-800x500.png",{"path":1192,"title":1193,"description":1194,"published":1195,"category":910,"image":1196,"draft":806},"\u002Fblog\u002Fsupabase-project-paused-recover","Supabase-Projekt pausiert? Deine Daten sind noch da","Supabase hat dein Projekt nach einer Woche ohne Aktivität pausiert. Gelöscht ist nichts, Wiederherstellen steht neben dem Namen, und du hast ein Jahr.","2026-09-12","\u002Fblog\u002Fsupabase-project-paused-recover\u002Fcard-800x500.png",{"path":1198,"title":1199,"description":1200,"published":1201,"category":802,"image":1202,"draft":806},"\u002Fblog\u002Fsafest-ai-app-builder","Welcher KI-App-Builder ist am sichersten? 30.998 Apps im Test","Welcher KI-App-Builder ist am sichersten? Wir haben 30.998 aktive Apps von Lovable, Base44, Replit, v0 und Bolt gescannt. Der Builder entscheidet das nicht.","2026-09-11","\u002Fblog\u002Fsafest-ai-app-builder\u002Fcard-800x500.png",{"path":605,"title":1204,"description":1205,"published":1206,"category":802,"image":1207,"draft":806},"Row Level Security für jede Supabase-Tabelle aktivieren und prüfen","Row Level Security in Supabase ohne Policy sperrt eine Tabelle komplett. Eine Policy ohne den Schalter bringt gar nichts. Hier ist das SQL, und der Test.","2026-09-10","\u002Fblog\u002Fenable-rls-on-every-supabase-table\u002Fcard-800x500.png",{"path":1209,"title":1210,"description":1211,"published":1212,"category":802,"image":1213,"draft":806},"\u002Fblog\u002Fsupabase-rls-disabled-in-public","Supabase \"RLS disabled in public\": was die Warnung übersieht","Supabase meldet \"RLS disabled in public\" als Fehler. Zur Lese-Policy, die deine Tabelle genauso offen lässt, sagt der Advisor gar nichts.","2026-09-09","\u002Fblog\u002Fsupabase-rls-disabled-in-public\u002Fcard-800x500.png",{"path":1215,"title":1216,"description":1217,"published":1218,"category":802,"image":1219,"draft":806},"\u002Fblog\u002Frotate-supabase-service-role-key","Geleakten Supabase-service_role-Schlüssel richtig tauschen","Supabase sagt: erst das Leck schließen. Andere Anleitungen: sofort tauschen. Was richtig ist, hängt davon ab, wohin dein service_role-Schlüssel gelangt ist.","2026-09-08","\u002Fblog\u002Frotate-supabase-service-role-key\u002Fcard-800x500.png",{"path":1221,"title":1222,"description":1223,"published":1224,"category":802,"image":1225,"draft":806},"\u002Fblog\u002Fvibe-coding-security-scanners-compared","Vibe-Coding-Security-Scanner im Vergleich, unserer eingeschlossen","Der beste Vibe-Coding-Security-Scanner hängt an drei Fragen, die keine Funktionsliste beantwortet. Acht Tools im Vergleich, mit Preisen und unseren Lücken.","2026-09-07","\u002Fblog\u002Fvibe-coding-security-scanners-compared\u002Fcard-800x500.png",{"path":1227,"title":1228,"description":1229,"published":1230,"category":802,"image":1231,"draft":806},"\u002Fblog\u002Fsupabase-security-checker","Supabase Security Checker: die fünf Prüfungen selbst machen","Ein Supabase Security Checker liest deine veröffentlichte App statt deiner Projekteinstellungen. Hier sind die fünf Prüfungen und wie du jede selbst ausführst.","2026-09-06","\u002Fblog\u002Fsupabase-security-checker\u002Fcard-800x500.png",{"path":1233,"title":1234,"description":1235,"published":1236,"category":802,"image":1237,"draft":806},"\u002Fblog\u002Fvibe-coding-security-scanner","Vibe-Coding-Security-Scanner: was ein URL-Scan übersieht","Ein Vibe-Coding-Security-Scanner liest deine laufende App von außen. Was das abdeckt, die vier Dinge, die er nicht sehen kann, und wie du das Ergebnis liest.","2026-09-05","\u002Fblog\u002Fvibe-coding-security-scanner\u002Fcard-800x500.png",{"path":1239,"title":1240,"description":1241,"published":1242,"category":802,"image":1243,"draft":806},"\u002Fblog\u002Fmissing-security-headers","Fehlende Sicherheitsheader: wann sie wirklich zählen","Fehlende Sicherheitsheader ist unser häufigster Befund. Wogegen sie schützen, und wann sie die unwichtigste Zeile in deinem Bericht sind.","2026-09-04","\u002Fblog\u002Fmissing-security-headers\u002Fcard-800x500.png",{"path":1245,"title":1246,"description":1247,"published":1248,"category":802,"image":1249,"draft":806},"\u002Fblog\u002Fopenai-api-key-exposed-in-frontend","Dein OpenAI-API-Schlüssel liegt im Frontend. Tausch ihn aus.","Ein OpenAI-API-Schlüssel im Frontend lässt sich an keine Domain binden. Tausch ihn heute, leg den Aufruf hinter deinen Server und begrenze das Budget.","2026-09-03","\u002Fblog\u002Fopenai-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1251,"title":1252,"description":1253,"published":1254,"category":802,"image":1255,"draft":806},"\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps","API-Schlüssel im Frontend: was 30.998 Apps wirklich ausliefern","Ein API-Schlüssel im Frontend ist meistens ein Google-Maps-Schlüssel. Wir haben 30.998 aktive Vibe-Coding-Apps gescannt und gezählt, was wirklich austritt.","2026-09-02","\u002Fblog\u002Fwhat-secrets-leak-from-vibe-coded-apps\u002Fcard-800x500.png",{"path":1257,"title":1258,"description":1259,"published":1260,"category":910,"image":1261,"draft":806},"\u002Fblog\u002Fsupabase-backup-tools-compared","Supabase-Backup-Tools im Vergleich, unseres eingeschlossen","Vier Arten von Supabase-Backup-Tool, was jedes wirklich kopiert und wann eine kostenlose GitHub Action jeden Anbieter schlägt, uns eingeschlossen.","2026-09-01","\u002Fblog\u002Fsupabase-backup-tools-compared\u002Fcard-800x500.png",{"path":1263,"title":1264,"description":1265,"published":1266,"category":802,"image":1267,"draft":806},"\u002Fblog\u002Freplit-secrets-explained","Secrets in Replit nutzen, und was trotzdem veröffentlicht wird","Secrets in Replit nutzen: anlegen, auslesen und die zwei Gründe beheben, warum undefined zurückkommt. Dazu die Schlüssel, die das Werkzeug nicht schützt.","2026-08-31","\u002Fblog\u002Freplit-secrets-explained\u002Fcard-800x500.png",{"path":1269,"title":1270,"description":1271,"published":1272,"category":910,"image":1273,"draft":806},"\u002Fblog\u002Fback-up-supabase-free-tier","Supabase Gratis-Tarif: Backup ohne Terminal selbst machen","Im Supabase Gratis-Tarif gibt es keine Backups, die Kopie musst du selbst machen. Wie das im Dashboard geht und was der CSV-Weg auslässt.","2026-08-30","\u002Fblog\u002Fback-up-supabase-free-tier\u002Fcard-800x500.png",{"path":1275,"title":1276,"description":1277,"published":1278,"category":802,"image":1279,"draft":806},"\u002Fblog\u002Fis-supabase-secure","Ist Supabase sicher? Ja. Dein Projekt ist eine eigene Frage","Ist Supabase sicher? Die Plattform ist auditiert, verschlüsselt und pentestet. Supabase schreibt selbst auf, wo das aufhört und deine Einstellungen anfangen.","2026-08-29","\u002Fblog\u002Fis-supabase-secure\u002Fcard-800x500.png",{"path":1281,"title":1282,"description":1283,"published":1284,"category":802,"image":1285,"draft":806},"\u002Fblog\u002Fwhere-to-find-supabase-api-keys","Wo du deine Supabase API-Keys findest: anon, service_role, URL","Projekt-URL, anon-Schlüssel und service_role-Schlüssel liegen auf einer Seite im Dashboard. Wo sie ist, und welcher der vier Werte in deine App gehört.","2026-08-28","\u002Fblog\u002Fwhere-to-find-supabase-api-keys\u002Fcard-800x500.png",{"path":555,"title":1287,"description":1288,"published":1289,"category":802,"image":1290,"draft":806},"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.","2026-08-27","\u002Fblog\u002Fnew-row-violates-row-level-security-policy\u002Fcard-800x500.png",{"path":1292,"title":1293,"description":1294,"published":1295,"category":802,"image":1296,"draft":806},"\u002Fblog\u002Fcors-wildcard-security-risk","Ist eine CORS-Wildcard ein Sicherheitsrisiko? Meist nicht.","Ist eine CORS-Wildcard ein Sicherheitsrisiko? Meist ist sie die Voreinstellung deines Builders und gibt nichts preis, was dein Server nicht ohnehin herausgab.","2026-08-26","\u002Fblog\u002Fcors-wildcard-security-risk\u002Fcard-800x500.png",{"path":1298,"title":1299,"description":1300,"published":1301,"category":910,"image":1302,"draft":806},"\u002Fblog\u002Fsupabase-branching-is-not-a-backup","Supabase Branching ist kein Backup. Es geht nur vorwärts.","Warum Supabase Branching kein Backup ist: Ein Branch startet ohne deine Daten, und ein Merge bringt nur das Schema. Wofür es gut ist und was stattdessen hilft.","2026-08-25","\u002Fblog\u002Fsupabase-branching-is-not-a-backup\u002Fcard-800x500.png",{"path":1304,"title":1305,"description":1306,"published":1307,"category":910,"image":1308,"draft":806},"\u002Fblog\u002Fhow-to-restore-a-supabase-backup","Supabase-Backup wiederherstellen, und was danach kaputt ist","Wie du ein Supabase-Backup aus dem Dashboard oder aus einer Dump-Datei wiederherstellst, was ersetzt wird und warum die App danach kaputt sein kann.","2026-08-24","\u002Fblog\u002Fhow-to-restore-a-supabase-backup\u002Fcard-800x500.png",{"path":1310,"title":1311,"description":1312,"published":1313,"category":802,"image":1314,"draft":806},"\u002Fblog\u002Fsupabase-storage-bucket-public","Dein Supabase-Storage-Bucket ist öffentlich. Ist das ein Problem?","Ein öffentlicher Supabase-Storage-Bucket heißt: Wer die URL einer Datei hat, kann sie öffnen. Er heißt nicht, dass jemand den Inhalt auflisten darf.","2026-08-23","\u002Fblog\u002Fsupabase-storage-bucket-public\u002Fcard-800x500.png",{"path":1316,"title":1317,"description":1318,"published":1319,"category":802,"image":1320,"draft":806},"\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend","Ist ein Google-API-Schlüssel im Frontend ein Problem?","Ein Google-API-Schlüssel im Frontend ist der Fund unseres Scanners Nummer eins, und meistens unbedenklich. Eine kostenlose Einstellung entscheidet darüber.","2026-08-22","\u002Fblog\u002Fgoogle-api-key-exposed-in-frontend\u002Fcard-800x500.png",{"path":1322,"title":1323,"description":1324,"published":1325,"category":910,"image":1326,"draft":806},"\u002Fblog\u002Fai-agent-deleted-my-database","Ein KI-Agent hat meine Supabase-Daten gelöscht. Was hole ich zurück?","Ein KI-Agent hat Daten aus deiner Datenbank gelöscht. Was du zurückholst, stand schon vorher fest, und die nächsten Minuten entscheiden über den Rest.","2026-08-21","\u002Fblog\u002Fai-agent-deleted-my-database\u002Fcard-800x500.png",{"path":1328,"title":1329,"description":1330,"published":1331,"category":802,"image":1332,"draft":806},"\u002Fblog\u002Fcan-anyone-read-your-supabase-database","Kann jeder deine Supabase-Datenbank lesen? 3.680 Apps im Test","Kann jeder deine Supabase-Datenbank ohne Login lesen? Wir haben 30.998 aktive Apps aus KI-Buildern gescannt und gemessen, wie oft die Antwort ja lautet.","2026-08-18","\u002Fblog\u002Fcan-anyone-read-your-supabase-database\u002Fcard-800x500.png",{"path":1334,"title":1335,"description":1336,"published":1331,"category":802,"image":1337,"draft":806},"\u002Fblog\u002Fsource-maps-exposed-in-production","Source-Maps öffentlich: deine App veröffentlicht ihren Quellcode","Eine öffentliche Source-Map lässt jeden den Originalcode deiner App lesen, Kommentare inklusive. Der 30-Sekunden-Check, und was wirklich zählt.","\u002Fblog\u002Fsource-maps-exposed-in-production\u002Fcard-800x500.png",{"path":1339,"title":1340,"description":1341,"published":1342,"category":802,"image":1343,"draft":806},"\u002Fblog\u002Fsupabase-new-api-keys","Supabase publishable key: ist er derselbe wie der anon key?","Supabase hat anon und service_role durch öffentliche und geheime Schlüssel ersetzt. Welcher gehört in deine App, und welcher nie?","2026-08-12","\u002Fblog\u002Fsupabase-new-api-keys\u002Fcard-800x500.png",{"path":1345,"title":1346,"description":1347,"published":1348,"category":910,"image":1349,"draft":806},"\u002Fblog\u002Fdoes-supabase-back-up-my-database","Sichert Supabase meine Datenbank? Das hängt von deinem Tarif ab.","Sichert Supabase deine Datenbank? Auf bezahlten Tarifen täglich, im Gratis-Tarif gar nicht. So findest du heraus, was du hast und was es nicht abdeckt.","2026-08-11","\u002Fblog\u002Fdoes-supabase-back-up-my-database\u002Fcard-800x500.png",{"path":1351,"title":1352,"description":1353,"published":1354,"category":802,"image":1355,"draft":806},"\u002Fblog\u002Fsupabase-rls-on-but-table-still-public","Supabase Row Level Security ist an. Die Tabelle ist trotzdem offen.","Supabase Row Level Security anzuschalten schützt keine Tabelle. Erst deine Policies tun das, und die, die deine App repariert hat, lässt oft alle herein.","2026-08-10","\u002Fblog\u002Fsupabase-rls-on-but-table-still-public\u002Fcard-800x500.png",{"path":1357,"title":1358,"description":1359,"published":1354,"category":910,"image":1360,"draft":806},"\u002Fblog\u002Fversion-history-is-not-a-backup","Der Versionsverlauf ist kein Backup. Er holt keine Tabelle zurück.","Lovable und Bolt führen einen Versionsverlauf für deinen Code. Deine Datenbank ist ein eigener Dienst, und ein Rücksprung holt deine Daten nicht zurück.","\u002Fblog\u002Fversion-history-is-not-a-backup\u002Fcard-800x500.png",{"path":1362,"title":1363,"description":1364,"published":1365,"category":910,"image":1366,"draft":806},"\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database","Drei Wege, eine Supabase-Datenbank zu sichern, und was fehlt","Das Dashboard, pg_dump und ein verwalteter Dienst. Was jeder Weg wirklich sichert, was er stillschweigend auslässt und welcher den Kontoverlust übersteht.","2026-08-09","\u002Fblog\u002Fthree-ways-to-back-up-a-supabase-database\u002Fcard-800x500.png",{"path":1368,"title":1369,"description":1370,"published":1365,"category":802,"image":1371,"draft":806},"\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend","Welche API-Schlüssel im Frontend sicher sind, und welche nicht","Dein Supabase-anon-Schlüssel darf öffentlich sein. Der service_role-Schlüssel nicht. Er ignoriert jede Regel, die du setzt. So erkennst du den Unterschied.","\u002Fblog\u002Fwhich-api-keys-are-safe-in-your-frontend\u002Fcard-800x500.png",1794213638724]