<?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: PatchAndShip</title>
    <description>The latest articles on DEV Community by PatchAndShip (@patchandship).</description>
    <link>https://dev.to/patchandship</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%2F4207904%2Fe5ba3fda-d46e-4b87-ae12-1e6f97e90cc3.png</url>
      <title>DEV Community: PatchAndShip</title>
      <link>https://dev.to/patchandship</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/patchandship"/>
    <language>en</language>
    <item>
      <title>Supabase error 42501 "permission denied for table": the safe fix before Oct 30</title>
      <dc:creator>PatchAndShip</dc:creator>
      <pubDate>Mon, 26 Oct 2026 06:50:05 +0000</pubDate>
      <link>https://dev.to/patchandship/supabase-error-42501-permission-denied-for-table-the-safe-fix-before-oct-30-1k23</link>
      <guid>https://dev.to/patchandship/supabase-error-42501-permission-denied-for-table-the-safe-fix-before-oct-30-1k23</guid>
      <description>&lt;p&gt;If a request to your Supabase table suddenly fails with &lt;code&gt;42501 "permission denied for table"&lt;/code&gt;, the role making the request has no grant on that table. It's a one-line error with a tempting one-line fix, and that fix is exactly what you want to avoid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Grant only what each role needs, keep RLS on with policies, and never &lt;code&gt;GRANT ALL&lt;/code&gt; to anon.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Status as of Oct 26, 2026: the change below hasn't reached existing projects yet. From Oct 30, Supabase is scheduled to apply it to all existing projects.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Supabase will change
&lt;/h2&gt;

&lt;p&gt;Here's how Supabase's changelog puts it: once the change reaches a project, "new tables you create in &lt;code&gt;public&lt;/code&gt; schema require an explicit opt-in (via a Postgres &lt;code&gt;grant&lt;/code&gt;) before the Data API can see them." The Data API is the auto-generated REST and GraphQL layer, the thing &lt;code&gt;supabase-js&lt;/code&gt; talks to.&lt;/p&gt;

&lt;p&gt;The timeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;April 28, 2026:&lt;/strong&gt; new projects could opt in at creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;May 30, 2026:&lt;/strong&gt; became the default for all new projects, rolled out gradually over a few weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;October 30, 2026:&lt;/strong&gt; from Oct 30, Supabase is scheduled to apply it to all existing projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What stays the same, per Supabase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Existing tables keep their current grants and stay reachable.&lt;/li&gt;
&lt;li&gt;RLS behavior is unchanged.&lt;/li&gt;
&lt;li&gt;Apps that only use a direct Postgres connection aren't affected.&lt;/li&gt;
&lt;li&gt;Only the &lt;code&gt;public&lt;/code&gt; schema is affected. &lt;code&gt;storage&lt;/code&gt;, &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;realtime&lt;/code&gt; and custom schemas aren't.&lt;/li&gt;
&lt;/ul&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: Tables not exposed to Data and GraphQL API automatically&lt;/a&gt; (published Apr 28, 2026).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the error looks like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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="err"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response also includes a hint with a suggested &lt;code&gt;GRANT&lt;/code&gt; statement. Read it before you paste it. The right grant depends on who should see the data.&lt;/p&gt;

&lt;p&gt;Same code, different cause: if the message says "new row violates row-level security policy", your grant is fine. An RLS policy's &lt;code&gt;with check&lt;/code&gt; rejected the row. Fix the policy, leave the grants alone, and don't turn RLS off. (&lt;a href="https://supabase.com/docs/guides/troubleshooting/database-api-42501-errors" rel="noopener noreferrer"&gt;Supabase: Database API 42501 errors&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;code&gt;GRANT ALL&lt;/code&gt; to anon is the wrong fix
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;anon&lt;/code&gt; is the role behind your app's public key, the one that ships in your JavaScript. Your AI tool may suggest &lt;code&gt;grant all on table ... to anon&lt;/code&gt;. The error disappears. Here's what you gave up to make that happen.&lt;/p&gt;

&lt;p&gt;Supabase spells out the order of checks: "If the grant is missing, Postgres rejects the query before RLS comes into play." (&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;) Grants decide whether a role can reach a table. RLS decides which rows it can see (&lt;a href="https://supabase.com/docs/guides/api/securing-your-api" rel="noopener noreferrer"&gt;Supabase: Securing your API&lt;/a&gt;). Once the grant is in place, RLS is the only thing between a visitor and your data.&lt;/p&gt;

&lt;p&gt;And if RLS is off on that table, Supabase's Security Advisor (lint 0013) doesn't sugarcoat it: "Anyone with your project URL can read, edit, and delete all data in this table because Row-Level Security is not enabled." (&lt;a href="https://supabase.com/docs/guides/database/database-advisors" rel="noopener noreferrer"&gt;Supabase: Database Advisors&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Supabase's advice: "Use both controls for every exposed object."&lt;/p&gt;

&lt;h2&gt;
  
  
  The safe fix: grant, RLS and policies in one migration
&lt;/h2&gt;

&lt;p&gt;Supabase's recommended fix treats three things as one unit: the grant, enabling RLS, and the policies you need. Ship them in one migration so none of them can go out without the others.&lt;/p&gt;

&lt;p&gt;Here's an example for an &lt;code&gt;orders&lt;/code&gt; table where each row belongs to one signed-in user via &lt;code&gt;user_id&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;begin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;orders&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;revoke&lt;/span&gt; &lt;span class="k"&gt;all&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;orders&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authenticated&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;orders&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="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="nv"&gt;"orders_select_own"&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;orders&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="k"&gt;select&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;"orders_insert_own"&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;orders&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="k"&gt;select&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;"orders_update_own"&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;orders&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;update&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="k"&gt;select&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;with&lt;/span&gt; &lt;span class="k"&gt;check&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;select&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;"orders_delete_own"&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;orders&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;delete&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="k"&gt;select&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;commit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Supabase's changelog example also grants &lt;code&gt;service_role&lt;/code&gt;. Add that if your own server code reads or writes the table:&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;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;orders&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only grant &lt;code&gt;anon&lt;/code&gt; access to data that signed-out visitors really need, and usually only &lt;code&gt;select&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test it:&lt;/strong&gt; sign out and read the table with just the public key. You should get an error or an empty list. Only test projects you own or are authorized to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  New projects hit this too
&lt;/h2&gt;

&lt;p&gt;New projects have behaved this way by default since the May 30, 2026 rollout, so a brand-new project may need the same grants today.&lt;/p&gt;

&lt;h2&gt;
  
  
  If an AI tool writes your migrations
&lt;/h2&gt;

&lt;p&gt;Give it the rule before it writes the migration. Paste this into your project instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When you add a new table to Supabase, write one migration that does three things together: grant only the access each role needs (never GRANT ALL to anon), enable Row Level Security on the table, and add one policy per operation that limits each signed-in user to their own rows. Do not turn Row Level Security off to make an error go away.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check what it writes against the example above before you run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Will my existing tables break?&lt;/strong&gt; No. Supabase says existing tables keep their grants and stay reachable, and RLS behavior is unchanged. The change only covers new tables in &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it affect storage or auth tables?&lt;/strong&gt; No. Only default privileges in &lt;code&gt;public&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I see affected tables?&lt;/strong&gt; In the Security Advisor. Supabase: "From today through October 30, the Security Advisor flags affected tables per project and shows the remediation SQL." Supabase also planned email notices to owners and admins of active projects on September 23 and October 23, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&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: Breaking Change: Tables not exposed to Data and GraphQL API automatically&lt;/a&gt; (Apr 28, 2026): timeline, scope, error 42501, recommended fix, Security Advisor.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.com/docs/guides/api/securing-your-api" rel="noopener noreferrer"&gt;Supabase Docs: Securing your API&lt;/a&gt;: grants and RLS.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.com/docs/guides/database/database-advisors" rel="noopener noreferrer"&gt;Supabase Docs: Database Advisors&lt;/a&gt;: lint 0013.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All checked Oct 26, 2026. The rule "grant only what each role needs…" is our own guidance.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The full guide, kept up to date after Oct 30, is &lt;a href="https://patchandship.com/blog/supabase-42501-permission-denied-oct-30" rel="noopener noreferrer"&gt;on our site&lt;/a&gt;. Want a second pair of eyes on your app? Try the &lt;a href="https://patchandship.com/check.html" rel="noopener noreferrer"&gt;free instant check&lt;/a&gt; (results in your browser in a few seconds, and it doesn't store your results) or ask for the &lt;a href="https://patchandship.com/mini-check.html" rel="noopener noreferrer"&gt;free emailed mini check&lt;/a&gt; (one page within 24 hours). For app owners or people they authorize.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PatchAndShip Team&lt;/p&gt;

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