<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FixLoop</title>
    <description>The latest articles on DEV Community by FixLoop (@fixloopdev).</description>
    <link>https://dev.to/fixloopdev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4190902%2F4defce50-e3af-4663-bdab-b19a86eeed7c.png</url>
      <title>DEV Community: FixLoop</title>
      <link>https://dev.to/fixloopdev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fixloopdev"/>
    <language>en</language>
    <item>
      <title>Lovable login works in preview, breaks in production: the 3 redirect lists</title>
      <dc:creator>FixLoop</dc:creator>
      <pubDate>Sun, 18 Oct 2026 17:04:15 +0000</pubDate>
      <link>https://dev.to/fixloopdev/lovable-login-works-in-preview-breaks-in-production-the-3-redirect-lists-4ip0</link>
      <guid>https://dev.to/fixloopdev/lovable-login-works-in-preview-breaks-in-production-the-3-redirect-lists-4ip0</guid>
      <description>&lt;p&gt;Login works on the Lovable preview. You publish, or put a custom domain in front, and now sign-in loops, drops you on &lt;code&gt;localhost:3000&lt;/code&gt;, or comes back with no session. The React didn't change. The host did.&lt;/p&gt;

&lt;p&gt;Supabase Auth only sends the browser back to URLs it already trusts. Preview was on that list. Production wasn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Preview: Google / GitHub / magic link works. Production: spinner, then the login page again.&lt;/li&gt;
&lt;li&gt;After OAuth you land on &lt;code&gt;http://localhost:3000&lt;/code&gt; or the old &lt;code&gt;*.lovable.app&lt;/code&gt; preview host.&lt;/li&gt;
&lt;li&gt;The address bar ends in &lt;code&gt;#error=...&lt;/code&gt; or &lt;code&gt;error_description=Unsupported%20redirect&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Google shows &lt;code&gt;Error 400: redirect_uri_mismatch&lt;/code&gt; naming a URL you don't recognize.&lt;/li&gt;
&lt;li&gt;Confirm / reset emails open the preview app, or 404 on the live domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test in a private window on the live origin. Preview cookies will lie to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are three lists, not one
&lt;/h2&gt;

&lt;p&gt;Most people paste the production URL into one box and call it done.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Site URL&lt;/strong&gt; (Authentication → URL Configuration). The default redirect when your code doesn't pass &lt;code&gt;redirectTo&lt;/code&gt;. Magic links and password resets use it. A new project starts at &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Redirect URLs&lt;/strong&gt; (same page). The allowlist. Every &lt;code&gt;redirectTo&lt;/code&gt; must match it, or Auth quietly ignores it and uses Site URL. That's how you "set production" and still bounce to localhost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The OAuth provider's Authorized redirect URI&lt;/strong&gt; (Google Cloud, GitHub). This is &lt;em&gt;not&lt;/em&gt; your app. It's Supabase's callback, &lt;code&gt;https://YOUR_PROJECT.supabase.co/auth/v1/callback&lt;/code&gt;. Publishing the Lovable app doesn't change it. Getting it wrong gives &lt;code&gt;redirect_uri_mismatch&lt;/code&gt; at the provider, which looks like a Lovable bug.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On a connected Supabase project, the first two live in the Supabase dashboard, not the Lovable chat. (Lovable Cloud, the built-in backend, is configured in Lovable instead.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Site URL&lt;/strong&gt; = the live origin, e.g. &lt;code&gt;https://myapp.com&lt;/code&gt; (or your &lt;code&gt;*.lovable.app&lt;/code&gt; host if that's what's published). Leave preview out of it, since it's the default for emails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redirect URLs&lt;/strong&gt;: add every origin you actually use. &lt;code&gt;**&lt;/code&gt; matches across &lt;code&gt;/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://myapp.com/**
https://myapp.com/auth/callback
https://*.lovable.app/**
http://localhost:5173/**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whatever path your code passes as &lt;code&gt;redirectTo&lt;/code&gt; must be covered. A trailing-slash mismatch is enough to fall back to Site URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider console&lt;/strong&gt; (Google / GitHub): JavaScript origins get your app's origin. Authorized redirect URIs get the Supabase callback copied from Authentication → Providers. Don't put &lt;code&gt;https://myapp.com/auth/callback&lt;/code&gt; there: Google returns to Supabase, and Supabase returns to your app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;redirectTo in code&lt;/strong&gt;: hardcoding the preview host is how production "randomly" returns to preview. Use the current origin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redirectTo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/callback`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signInWithOAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;redirectTo&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for &lt;code&gt;resetPasswordForEmail&lt;/code&gt; and &lt;code&gt;signInWithOtp&lt;/code&gt;. If you customized email templates, &lt;code&gt;{{ .SiteURL }}&lt;/code&gt; always points at Site URL; use &lt;code&gt;{{ .RedirectTo }}&lt;/code&gt; when the link should follow what you passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  60-second checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Private window, live hostname.&lt;/li&gt;
&lt;li&gt;Site URL is production, not localhost or only the preview host.&lt;/li&gt;
&lt;li&gt;Redirect URLs cover production (plus &lt;code&gt;/auth/callback&lt;/code&gt; if you use it); preview can stay as a wildcard.&lt;/li&gt;
&lt;li&gt;Provider redirect URI is &lt;code&gt;https://YOUR_PROJECT.supabase.co/auth/v1/callback&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redirectTo&lt;/code&gt; is built from &lt;code&gt;window.location.origin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read the &lt;code&gt;error&lt;/code&gt; / &lt;code&gt;error_description&lt;/code&gt; in the URL fragment on the landing page. Auth still redirects on failure; the reason is in the hash.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Full guide
&lt;/h2&gt;

&lt;p&gt;The longer version with source links and an FAQ (Site URL vs Redirect URLs, "I set Site URL, why localhost?", Lovable vs Supabase settings) is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://fixloop.dev/guides/lovable-login-preview-vs-production" rel="noopener noreferrer"&gt;https://fixloop.dev/guides/lovable-login-preview-vs-production&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If sessions exist but saves silently do nothing, that's RLS, not redirects. I wrote up a &lt;a href="https://dev.to/fixloopdev/is-my-supabase-database-public-a-5-minute-check-with-the-anon-key-2iil"&gt;5-minute check for that&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run &lt;a href="https://fixloop.dev" rel="noopener noreferrer"&gt;FixLoop&lt;/a&gt;. There's a &lt;a href="https://fixloop.dev/scan" rel="noopener noreferrer"&gt;free scan&lt;/a&gt; for leaked keys and missing headers, and we fix broken Lovable/Supabase apps for a fixed price. No fix, no pay.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>lovable</category>
      <category>webdev</category>
      <category>auth</category>
    </item>
    <item>
      <title>Is my Supabase database public? A 5-minute check with the anon key</title>
      <dc:creator>FixLoop</dc:creator>
      <pubDate>Sat, 17 Oct 2026 20:09:09 +0000</pubDate>
      <link>https://dev.to/fixloopdev/is-my-supabase-database-public-a-5-minute-check-with-the-anon-key-2iil</link>
      <guid>https://dev.to/fixloopdev/is-my-supabase-database-public-a-5-minute-check-with-the-anon-key-2iil</guid>
      <description>&lt;p&gt;If you shipped a Lovable (or any Supabase) app, your anon key is in the browser. That's fine, it's supposed to be. The real question is different: &lt;strong&gt;can a stranger read your tables with that key and no login?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Row Level Security is off on a table that still has grants to &lt;code&gt;anon&lt;/code&gt;, the answer is yes. Here's a 5-minute check you can run on your own project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the anon key isn't the leak
&lt;/h2&gt;

&lt;p&gt;The anon key is a public JWT. Postgres then decides what it can do in two layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grants&lt;/strong&gt; decide whether &lt;code&gt;anon&lt;/code&gt; can SELECT / INSERT / UPDATE / DELETE the table at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RLS&lt;/strong&gt; decides which rows survive that grant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RLS off means the second layer never runs. If &lt;code&gt;anon&lt;/code&gt; also has a grant (the old default on &lt;code&gt;public&lt;/code&gt; tables), every row is reachable from the REST endpoint. A policy of &lt;code&gt;using (true)&lt;/code&gt; is the same hole with RLS "on": the dashboard badge looks fine, the table is still public.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check
&lt;/h2&gt;

&lt;p&gt;Open your live app in a private window, DevTools → Network, filter &lt;code&gt;rest/v1&lt;/code&gt;. Grab the project host (&lt;code&gt;abcdefghij.supabase.co&lt;/code&gt;), a table name from the path, and the &lt;code&gt;apikey&lt;/code&gt; header from a signed-out request. &lt;strong&gt;Never use the &lt;code&gt;service_role&lt;/code&gt; key&lt;/strong&gt; for this (or anywhere in the client).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://YOUR_PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&amp;amp;limit=5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"apikey: YOUR_ANON_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_ANON_KEY"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No cookies, no session. If this returns user data, anyone can do it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200 + rows&lt;/strong&gt;: anon can read the table. RLS is off or a policy allows anonymous SELECT. Fix this first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;200 + &lt;code&gt;[]&lt;/code&gt;&lt;/strong&gt;: there's a SELECT grant but no rows passed RLS (or the table is empty). Retry on a table you know has data. If the signed-in app shows rows and anon still gets &lt;code&gt;[]&lt;/code&gt;, you're probably fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;401&lt;/strong&gt;: bad or truncated key. This does &lt;em&gt;not&lt;/em&gt; mean the table is private.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;403 / 42501&lt;/strong&gt;: anon has no grant, so the request never reaches RLS. After Oct 30, 2026 this shows up a lot on new tables (&lt;a href="https://dev.to/fixloopdev/supabase-oct-30-why-new-tables-get-42501-and-how-to-fix-grants-rls-2i8k"&gt;why&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;404 / PGRST205&lt;/strong&gt;: wrong table name. Use the one from the network tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The safe fix
&lt;/h2&gt;

&lt;p&gt;Turn RLS on and scope policies to &lt;code&gt;auth.uid()&lt;/code&gt;. Don't "fix" an open table by granting more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"select own rows"&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"insert own rows"&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't do any of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GRANT ALL ON ALL TABLES IN SCHEMA public TO anon;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;any grant to anon while RLS is off&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;using (true)&lt;/code&gt; on a table with private rows&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;service_role&lt;/code&gt; key in frontend env&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Lovable, put the same rules in your project instructions so the next migration doesn't recreate the hole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full guide
&lt;/h2&gt;

&lt;p&gt;The longer version with the browser-console variant, a checklist, and an FAQ (should I rotate the anon key? Lovable says RLS is on, can it still be public?) is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://fixloop.dev/guides/is-my-supabase-database-public" rel="noopener noreferrer"&gt;https://fixloop.dev/guides/is-my-supabase-database-public&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run &lt;a href="https://fixloop.dev" rel="noopener noreferrer"&gt;FixLoop&lt;/a&gt;. There's a &lt;a href="https://fixloop.dev/scan" rel="noopener noreferrer"&gt;free scan&lt;/a&gt; for leaked keys and missing headers, and we fix broken Lovable/Supabase apps for a fixed price. No fix, no pay.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>security</category>
      <category>webdev</category>
      <category>lovable</category>
    </item>
    <item>
      <title>Supabase Oct 30: why new tables get 42501 (and how to fix grants + RLS)</title>
      <dc:creator>FixLoop</dc:creator>
      <pubDate>Sat, 17 Oct 2026 17:14:20 +0000</pubDate>
      <link>https://dev.to/fixloopdev/supabase-oct-30-why-new-tables-get-42501-and-how-to-fix-grants-rls-2i8k</link>
      <guid>https://dev.to/fixloopdev/supabase-oct-30-why-new-tables-get-42501-and-how-to-fix-grants-rls-2i8k</guid>
      <description>&lt;p&gt;If you just created a table in Lovable, turned on RLS, wrote policies, and the app still returns &lt;code&gt;permission denied for table&lt;/code&gt;, this is the one.&lt;/p&gt;

&lt;p&gt;Starting &lt;strong&gt;October 30, 2026&lt;/strong&gt;, existing Supabase projects stop auto-granting privileges on new tables in the &lt;code&gt;public&lt;/code&gt; schema. The table is there. The policies are there. The Data API still cannot see it.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically" rel="noopener noreferrer"&gt;Supabase changelog&lt;/a&gt; · &lt;a href="https://supabase.com/docs/guides/api/securing-your-api" rel="noopener noreferrer"&gt;Securing your API&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;The request fails with Postgres error &lt;strong&gt;42501&lt;/strong&gt;. PostgREST (the Data API behind &lt;code&gt;supabase-js&lt;/code&gt;) sends something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42501"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"permission denied for table your_table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grant the required privileges to the current role with: GRANT SELECT ON public.your_table TO anon;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical setup: a Lovable-created table, RLS enabled, policies that should allow the row, and it still fails. Old tables keep working. Only the new one fails.&lt;/p&gt;

&lt;p&gt;The hint names the missing grant. It is &lt;strong&gt;not&lt;/strong&gt; a recommendation to copy blindly. Read it, then grant only what that role should have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grants vs RLS
&lt;/h2&gt;

&lt;p&gt;Postgres runs two checks, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grants&lt;/strong&gt; decide whether a role can touch the table at all (&lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Row Level Security&lt;/strong&gt; decides which rows that role can see or change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the grant is missing, RLS never runs. That is why "I have policies" does not fix 42501.&lt;/p&gt;

&lt;p&gt;Until now, a new table in &lt;code&gt;public&lt;/code&gt; automatically received select/insert/update/delete for &lt;code&gt;anon&lt;/code&gt;, &lt;code&gt;authenticated&lt;/code&gt;, and &lt;code&gt;service_role&lt;/code&gt;. RLS was the only gate. New projects already dropped that default. On October 30 it applies to every existing project too.&lt;/p&gt;

&lt;p&gt;Existing tables keep their grants. Direct Postgres connections are not affected. This is a Data API change: REST, GraphQL, and the client libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Put grants in the same migration as the table, RLS, and policies. Least privilege, not &lt;code&gt;GRANT ALL&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- signed-in users: the operations your app actually uses&lt;/span&gt;
&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;authenticated&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- server code / Edge Functions using the service_role key&lt;/span&gt;
&lt;span class="k"&gt;grant&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;your_table&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;service_role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- only if signed-out visitors should read this table&lt;/span&gt;
&lt;span class="c1"&gt;-- grant select on table public.your_table to anon;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave &lt;code&gt;anon&lt;/code&gt; with nothing unless the table is meant to be public. Skip &lt;code&gt;GRANT ALL ON ALL TABLES IN SCHEMA public&lt;/code&gt; - that also opens tables where RLS was never turned on.&lt;/p&gt;

&lt;p&gt;If you already see 42501, check who has what:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;grantee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;privilege_type&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;role_table_grants&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;table_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
 &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'your_table'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tell Lovable to include grants
&lt;/h2&gt;

&lt;p&gt;Paste this into project instructions / Knowledge before the next migration:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you create or change a Supabase table, put all of this in the same migration: CREATE TABLE, ENABLE ROW LEVEL SECURITY, CREATE POLICY, GRANT to authenticated and service_role, and GRANT SELECT to anon only if signed-out users should read it. Do not use GRANT ALL. Do not skip grants because RLS is on.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Full checklist + FAQ
&lt;/h2&gt;

&lt;p&gt;The longer version with the Lovable paste block, checklist, and FAQ lives here (canonical):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://fixloop.dev/guides/supabase-oct-30-grants-change" rel="noopener noreferrer"&gt;https://fixloop.dev/guides/supabase-oct-30-grants-change&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run &lt;a href="https://fixloop.dev" rel="noopener noreferrer"&gt;FixLoop&lt;/a&gt; - we fix broken Lovable/Supabase apps for a fixed price. No fix, no pay.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>webdev</category>
      <category>security</category>
      <category>lovable</category>
    </item>
  </channel>
</rss>
