[{"data":1,"prerenderedAt":1523},["ShallowReactive",2],{"/blog/supabase-permission-denied-new-tables-october-30-2026/":3,"/blog/supabase-permission-denied-new-tables-october-30-2026/-related":742,"/blog/supabase-permission-denied-new-tables-october-30-2026/-resources":1520},{"id":4,"title":5,"author":6,"body":10,"category":692,"createdAt":693,"description":694,"disclosure":695,"extension":696,"faq":697,"location":713,"meta":714,"navigation":345,"ogImage":695,"path":720,"primaryKeyword":721,"readingTime":722,"seo":723,"sources":724,"stem":732,"tags":733,"takeaways":734,"updatedAt":693,"url":740,"__hash__":741},"blog/blog/supabase-permission-denied-new-tables-october-30-2026.md","Supabase \"Permission Denied\" on New Tables: the October 30 Fix",{"name":7,"social":8},"Simon Dziak",{"twitter":9},"@SzymonDziak",{"type":11,"value":12,"toc":685},"minimark",[13,42,51,56,66,125,163,166,204,208,211,276,295,299,308,499,502,569,573,653,657,673,681],[14,15,16,20,21,25,26,29,30,33,34,37,38,41],"p",{},[17,18,19],"strong",{},"From October 30, 2026, a table you add to an existing Supabase project is invisible to your app until you grant it."," Supabase used to give every new table in the ",[22,23,24],"code",{},"public"," schema to the ",[22,27,28],{},"anon",", ",[22,31,32],{},"authenticated"," and ",[22,35,36],{},"service_role"," roles automatically. It now leaves that to you. Tables you already have keep working; a table created after the change returns ",[22,39,40],{},"permission denied for table"," until its migration includes the grants.",[14,43,44,45,50],{},"To see which tables in your migrations are affected, paste them into the free ",[46,47,49],"a",{"href":48},"/tools/supabase-oct-30-checker/","Supabase October 30 grants checker",". It runs in your browser and never uploads your SQL.",[52,53,55],"h2",{"id":54},"what-changes-and-when","What changes, and when",[14,57,58,59,65],{},"Supabase announced the change in its ",[46,60,64],{"href":61,"rel":62},"https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically",[63],"nofollow","changelog on April 28, 2026",":",[67,68,69,82],"table",{},[70,71,72],"thead",{},[73,74,75,79],"tr",{},[76,77,78],"th",{},"Date",[76,80,81],{},"What happens",[83,84,85,94,105,113],"tbody",{},[73,86,87,91],{},[88,89,90],"td",{},"April 28, 2026",[88,92,93],{},"New projects can opt out of exposing new tables when they are created",[73,95,96,99],{},[88,97,98],{},"May 18, 2026",[88,100,101,104],{},[22,102,103],{},"pg_graphql"," is no longer enabled by default",[73,106,107,110],{},[88,108,109],{},"May 30, 2026",[88,111,112],{},"New projects stop exposing new tables by default (rolled out over a few weeks)",[73,114,115,120],{},[88,116,117],{},[17,118,119],{},"October 30, 2026",[88,121,122],{},[17,123,124],{},"The same default applies to all existing projects",[14,126,127,128,132,133,135,136,139,140,29,142,33,144,146,147,150,151,154,155,158,159,162],{},"What changes is Postgres's ",[129,130,131],"em",{},"default privileges"," in the ",[22,134,24],{}," schema. Before, every new table was granted ",[22,137,138],{},"select, insert, update, delete"," to ",[22,141,28],{},[22,143,32],{},[22,145,36],{},", new sequences got ",[22,148,149],{},"usage, select",", and new functions got ",[22,152,153],{},"execute",". After the change those defaults are gone, so the Data API (REST at ",[22,156,157],{},"/rest/v1"," and GraphQL at ",[22,160,161],{},"/graphql/v1",") can't see a new table until a role is granted on it.",[14,164,165],{},"What doesn't change:",[167,168,169,176,182,198],"ul",{},[170,171,172,175],"li",{},[17,173,174],{},"Existing tables"," keep the grants they already have.",[170,177,178,181],{},[17,179,180],{},"Row-level security"," works exactly as before.",[170,183,184,187,188,29,191,33,194,197],{},[17,185,186],{},"Other schemas",", such as ",[22,189,190],{},"auth",[22,192,193],{},"storage",[22,195,196],{},"realtime",", aren't affected.",[170,199,200,203],{},[17,201,202],{},"Direct Postgres connections"," (psql, an ORM or your own server) aren't affected. The change matters if your app talks to Supabase through supabase-js, REST or GraphQL.",[52,205,207],{"id":206},"the-symptom","The symptom",[14,209,210],{},"A request to a new table without grants fails with Postgres error 42501:",[212,213,218],"pre",{"className":214,"code":215,"language":216,"meta":217,"style":217},"language-json shiki shiki-themes github-light github-dark","{\n  \"code\": \"42501\",\n  \"message\": \"permission denied for table projects\",\n  \"hint\": \"Grant the required privileges to the current role with: GRANT SELECT ON public.projects TO anon;\"\n}\n","json","",[22,219,220,229,246,259,270],{"__ignoreMap":217},[221,222,225],"span",{"class":223,"line":224},"line",1,[221,226,228],{"class":227},"sVt8B","{\n",[221,230,232,236,239,243],{"class":223,"line":231},2,[221,233,235],{"class":234},"sj4cs","  \"code\"",[221,237,238],{"class":227},": ",[221,240,242],{"class":241},"sZZnC","\"42501\"",[221,244,245],{"class":227},",\n",[221,247,249,252,254,257],{"class":223,"line":248},3,[221,250,251],{"class":234},"  \"message\"",[221,253,238],{"class":227},[221,255,256],{"class":241},"\"permission denied for table projects\"",[221,258,245],{"class":227},[221,260,262,265,267],{"class":223,"line":261},4,[221,263,264],{"class":234},"  \"hint\"",[221,266,238],{"class":227},[221,268,269],{"class":241},"\"Grant the required privileges to the current role with: GRANT SELECT ON public.projects TO anon;\"\n",[221,271,273],{"class":223,"line":272},5,[221,274,275],{"class":227},"}\n",[14,277,278,279,282,283,286,287,290,291,294],{},"In supabase-js, the ",[22,280,281],{},"error"," object comes back filled and ",[22,284,285],{},"data"," is ",[22,288,289],{},"null",". If the app doesn't show errors, users simply see an empty screen or a button that does nothing. The same query works in the SQL editor, which runs as ",[22,292,293],{},"postgres",", the role that owns the table and has every privilege, and fails in the app. That is why this one can be confusing.",[52,296,298],{"id":297},"the-fix-grant-in-the-same-migration","The fix: grant in the same migration",[14,300,301,302,307],{},"Put the grants in the migration that creates the table, so every environment (local, staging, production) ends up the same. This is the pattern from Supabase's ",[46,303,306],{"href":304,"rel":305},"https://supabase.com/docs/guides/api/securing-your-api",[63],"Securing your API"," guide:",[212,309,313],{"className":310,"code":311,"language":312,"meta":217,"style":217},"language-sql shiki shiki-themes github-light github-dark","create table public.projects (\n  id uuid primary key default gen_random_uuid(),\n  user_id uuid not null references auth.users on delete cascade,\n  name text not null\n);\n\n-- 1. Who can reach the table at all\ngrant select, insert, update, delete on table public.projects to authenticated;\ngrant select, insert, update, delete on table public.projects to service_role;\n\n-- 2. Which rows they can see and change\nalter table public.projects enable row level security;\n\ncreate policy \"Owners can read their projects\"\n  on public.projects for select\n  to authenticated\n  using ((select auth.uid()) = user_id);\n\ncreate policy \"Owners can add projects\"\n  on public.projects for insert\n  to authenticated\n  with check ((select auth.uid()) = user_id);\n\ncreate policy \"Owners can update their projects\"\n  on public.projects for update\n  to authenticated\n  using ((select auth.uid()) = user_id)\n  with check ((select auth.uid()) = user_id);\n\ncreate policy \"Owners can delete their projects\"\n  on public.projects for delete\n  to authenticated\n  using ((select auth.uid()) = user_id);\n","sql",[22,314,315,320,325,330,335,340,347,353,359,365,370,376,382,387,393,399,405,411,416,422,428,433,439,444,450,456,461,467,472,477,483,489,494],{"__ignoreMap":217},[221,316,317],{"class":223,"line":224},[221,318,319],{},"create table public.projects (\n",[221,321,322],{"class":223,"line":231},[221,323,324],{},"  id uuid primary key default gen_random_uuid(),\n",[221,326,327],{"class":223,"line":248},[221,328,329],{},"  user_id uuid not null references auth.users on delete cascade,\n",[221,331,332],{"class":223,"line":261},[221,333,334],{},"  name text not null\n",[221,336,337],{"class":223,"line":272},[221,338,339],{},");\n",[221,341,343],{"class":223,"line":342},6,[221,344,346],{"emptyLinePlaceholder":345},true,"\n",[221,348,350],{"class":223,"line":349},7,[221,351,352],{},"-- 1. Who can reach the table at all\n",[221,354,356],{"class":223,"line":355},8,[221,357,358],{},"grant select, insert, update, delete on table public.projects to authenticated;\n",[221,360,362],{"class":223,"line":361},9,[221,363,364],{},"grant select, insert, update, delete on table public.projects to service_role;\n",[221,366,368],{"class":223,"line":367},10,[221,369,346],{"emptyLinePlaceholder":345},[221,371,373],{"class":223,"line":372},11,[221,374,375],{},"-- 2. Which rows they can see and change\n",[221,377,379],{"class":223,"line":378},12,[221,380,381],{},"alter table public.projects enable row level security;\n",[221,383,385],{"class":223,"line":384},13,[221,386,346],{"emptyLinePlaceholder":345},[221,388,390],{"class":223,"line":389},14,[221,391,392],{},"create policy \"Owners can read their projects\"\n",[221,394,396],{"class":223,"line":395},15,[221,397,398],{},"  on public.projects for select\n",[221,400,402],{"class":223,"line":401},16,[221,403,404],{},"  to authenticated\n",[221,406,408],{"class":223,"line":407},17,[221,409,410],{},"  using ((select auth.uid()) = user_id);\n",[221,412,414],{"class":223,"line":413},18,[221,415,346],{"emptyLinePlaceholder":345},[221,417,419],{"class":223,"line":418},19,[221,420,421],{},"create policy \"Owners can add projects\"\n",[221,423,425],{"class":223,"line":424},20,[221,426,427],{},"  on public.projects for insert\n",[221,429,431],{"class":223,"line":430},21,[221,432,404],{},[221,434,436],{"class":223,"line":435},22,[221,437,438],{},"  with check ((select auth.uid()) = user_id);\n",[221,440,442],{"class":223,"line":441},23,[221,443,346],{"emptyLinePlaceholder":345},[221,445,447],{"class":223,"line":446},24,[221,448,449],{},"create policy \"Owners can update their projects\"\n",[221,451,453],{"class":223,"line":452},25,[221,454,455],{},"  on public.projects for update\n",[221,457,459],{"class":223,"line":458},26,[221,460,404],{},[221,462,464],{"class":223,"line":463},27,[221,465,466],{},"  using ((select auth.uid()) = user_id)\n",[221,468,470],{"class":223,"line":469},28,[221,471,438],{},[221,473,475],{"class":223,"line":474},29,[221,476,346],{"emptyLinePlaceholder":345},[221,478,480],{"class":223,"line":479},30,[221,481,482],{},"create policy \"Owners can delete their projects\"\n",[221,484,486],{"class":223,"line":485},31,[221,487,488],{},"  on public.projects for delete\n",[221,490,492],{"class":223,"line":491},32,[221,493,404],{},[221,495,497],{"class":223,"line":496},33,[221,498,410],{},[14,500,501],{},"A few details are easy to miss:",[167,503,504,517,537,560],{},[170,505,506,509,510,512,513,516],{},[17,507,508],{},"Grant only what each role needs."," A table that signed-out visitors should never touch gets no ",[22,511,28],{}," grant at all. Public read-only data gets ",[22,514,515],{},"grant select ... to anon"," and nothing more.",[170,518,519,525,526,528,529,532,533,536],{},[17,520,521,524],{},[22,522,523],{},"serial"," columns need a sequence grant."," If the table uses ",[22,527,523],{}," or ",[22,530,531],{},"bigserial"," rather than an identity column or a UUID, inserts also need ",[22,534,535],{},"grant usage, select on sequence public.projects_id_seq to authenticated;",".",[170,538,539,544,545,548,549,552,553,556,557,559],{},[17,540,541,542,536],{},"Functions called over the API need ",[22,543,153],{}," For a function your app calls with ",[22,546,547],{},"rpc()",", use ",[22,550,551],{},"grant execute on function public.your_function() to authenticated;",". A ",[22,554,555],{},"security definer"," function runs with its owner's rights and bypasses RLS, so grant ",[22,558,153],{}," only to roles that should call it, and review those functions.",[170,561,562,565,566,568],{},[17,563,564],{},"Grants are not a replacement for RLS."," With a grant and RLS off, any signed-in user, or anyone at all if ",[22,567,28],{}," is granted, can read every row. Turn RLS on for every exposed table.",[52,570,572],{"id":571},"check-your-project-before-october-30","Check your project before October 30",[574,575,576,597,606,614],"ol",{},[170,577,578,581,582,585,586,589,590,592,593,596],{},[17,579,580],{},"List your newest migrations"," (",[22,583,584],{},"supabase/migrations/"," in the repo, or the SQL your AI tool generated) and look for ",[22,587,588],{},"create table"," statements in ",[22,591,24],{}," without matching ",[22,594,595],{},"grant"," lines.",[170,598,599,605],{},[17,600,601,602,536],{},"Run them through the ",[46,603,604],{"href":48},"grants checker"," It lists each table that would need a grant, each table with RLS off, and each policy that lets anyone in.",[170,607,608,613],{},[17,609,610,611,536],{},"Test as a real user, not as ",[22,612,293],{}," Sign in to the app with a normal account and use each feature that touches a new table. The SQL editor bypasses grants, so it won't show the problem.",[170,615,616,619,620,624,625,649,652],{},[17,617,618],{},"Opt in early if you can."," Supabase lets existing projects switch to the new default before October 30. The ",[46,621,623],{"href":61,"rel":622},[63],"changelog"," gives the SQL to run in the SQL editor; it changes only tables and sequences created afterwards:",[212,626,628],{"className":310,"code":627,"language":312,"meta":217,"style":217},"alter default privileges for role postgres in schema public\n  revoke select, insert, update, delete on tables from anon, authenticated, service_role;\nalter default privileges for role postgres in schema public\n  revoke usage, select on sequences from anon, authenticated, service_role;\n",[22,629,630,635,640,644],{"__ignoreMap":217},[221,631,632],{"class":223,"line":224},[221,633,634],{},"alter default privileges for role postgres in schema public\n",[221,636,637],{"class":223,"line":231},[221,638,639],{},"  revoke select, insert, update, delete on tables from anon, authenticated, service_role;\n",[221,641,642],{"class":223,"line":248},[221,643,634],{},[221,645,646],{"class":223,"line":261},[221,647,648],{},"  revoke usage, select on sequences from anon, authenticated, service_role;\n",[650,651],"br",{},"Doing it on a staging project first shows you any missing grants on your schedule instead of the deadline's.",[52,654,656],{"id":655},"if-you-use-lovable-bolt-or-another-ai-builder","If you use Lovable, Bolt or another AI builder",[14,658,659,660,662,663,665,666,668,669,672],{},"These tools write SQL migrations for you, and nothing in a generated migration warns you that the defaults changed. Read each new migration before you approve it: every ",[22,661,588],{}," in ",[22,664,24],{}," should be followed by a ",[22,667,595],{}," for each role that needs it and by ",[22,670,671],{},"enable row level security",". If you can't read the SQL yourself, paste it into the checker; it flags missing grants in plain language.",[14,674,675,676,680],{},"If your AI-built app has more than a couple of these problems, or users are already seeing empty screens, App369's ",[46,677,679],{"href":678},"/fixed-price/ai-app-rescue/","AI-Built App Rescue"," starts with a $450 Launch Check. Within 3 business days of read-only access, you get each issue it finds in sign-in, data access, payments and deploys, ranked, with where it is, plus a fixed price to fix the worst of them. The $450 is credited to that fix.",[682,683,684],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":217,"searchDepth":231,"depth":231,"links":686},[687,688,689,690,691],{"id":54,"depth":231,"text":55},{"id":206,"depth":231,"text":207},{"id":297,"depth":231,"text":298},{"id":571,"depth":231,"text":572},{"id":655,"depth":231,"text":656},"AI & Technology","2026-10-15T18:00:00.000Z","From October 30, 2026, new Supabase tables return \"permission denied for table\" (42501) until you grant them. What changes, who is affected, and the exact SQL to fix it.",null,"md",[698,701,704,707,710],{"question":699,"answer":700},"Why does Supabase say \"permission denied for table\" on a table I just created?","Since the Data API change (May 30, 2026 for new projects, October 30, 2026 for existing ones), new tables in the public schema have no grants for anon, authenticated or service_role. Add the grants in the migration that creates the table.",{"question":702,"answer":703},"Will my existing Supabase tables stop working on October 30?","No. Supabase says existing tables keep their current grants. Only tables created after the change need explicit grants.",{"question":705,"answer":706},"Is this the same as a row-level security error?","No. A missing grant fails every request with \"permission denied for table\". RLS blocks differently: reads return no rows, updates and deletes on hidden rows change nothing, and a blocked insert fails with \"new row violates row-level security policy\".",{"question":708,"answer":709},"Can I turn the change off for an existing project?","Supabase documents a rollback that restores the old default privileges, but that exposes every future table again. Granting each table on purpose is safer.",{"question":711,"answer":712},"Does this affect Lovable, Bolt or other AI-built apps?","It affects any project that reaches new tables through the Data API (supabase-js, REST or GraphQL), whoever wrote the migrations, which covers most AI-built apps. Apps that only use a direct Postgres connection are not affected. Check new migrations for a grant per role before they run.","Boynton Beach, FL",{"readingTimeStats":715},{"text":716,"minutes":717,"time":718,"words":719},"5 min read",4.895,293700,979,"/blog/supabase-permission-denied-new-tables-october-30-2026","supabase permission denied for table","7 min",{"title":5,"description":694},[725,727,729],{"title":726,"url":61},"Supabase changelog: tables not exposed to Data and GraphQL API automatically (April 28, 2026)",{"title":728,"url":304},"Supabase docs: Securing your API",{"title":730,"url":731},"PostgreSQL docs: GRANT","https://www.postgresql.org/docs/current/sql-grant.html","blog/supabase-permission-denied-new-tables-october-30-2026","supabase permission denied, supabase 42501, supabase grants, supabase data api, supabase october 30, supabase new tables, lovable supabase, row level security",[735,736,737,738,739],"On October 30, 2026, Supabase stops granting new tables in the public schema to anon, authenticated and service_role in existing projects. New projects have worked this way since May 30.","Tables you already have keep their grants and keep working. Only tables created after the change are affected.","A new table without grants fails with error 42501, \"permission denied for table\", in supabase-js, the REST API and GraphQL.","The fix is one grant per role in the same migration that creates the table, next to row-level security and its policies.","Grants decide whether a role can reach a table at all; RLS policies decide which rows it sees. You need both.","https://app369.com/blog/supabase-permission-denied-new-tables-october-30-2026/","x4fZ8H-RT1J1XVdgRfaaH_PR7URQHo8QR_00_f95qig",[743,1200],{"id":4,"title":5,"author":744,"body":746,"category":692,"createdAt":693,"description":694,"disclosure":695,"extension":696,"faq":1186,"location":713,"meta":1192,"navigation":345,"ogImage":695,"path":720,"primaryKeyword":721,"readingTime":722,"seo":1194,"sources":1195,"stem":732,"tags":733,"takeaways":1199,"updatedAt":693,"url":740,"__hash__":741},{"name":7,"social":745},{"twitter":9},{"type":11,"value":747,"toc":1179},[748,762,766,768,773,815,837,839,863,865,867,907,917,919,924,1060,1062,1104,1106,1161,1163,1173,1177],[14,749,750,20,752,25,754,29,756,33,758,37,760,41],{},[17,751,19],{},[22,753,24],{},[22,755,28],{},[22,757,32],{},[22,759,36],{},[22,761,40],{},[14,763,44,764,50],{},[46,765,49],{"href":48},[52,767,55],{"id":54},[14,769,58,770,65],{},[46,771,64],{"href":61,"rel":772},[63],[67,774,775,783],{},[70,776,777],{},[73,778,779,781],{},[76,780,78],{},[76,782,81],{},[83,784,785,791,799,805],{},[73,786,787,789],{},[88,788,90],{},[88,790,93],{},[73,792,793,795],{},[88,794,98],{},[88,796,797,104],{},[22,798,103],{},[73,800,801,803],{},[88,802,109],{},[88,804,112],{},[73,806,807,811],{},[88,808,809],{},[17,810,119],{},[88,812,813],{},[17,814,124],{},[14,816,127,817,132,819,135,821,139,823,29,825,33,827,146,829,150,831,154,833,158,835,162],{},[129,818,131],{},[22,820,24],{},[22,822,138],{},[22,824,28],{},[22,826,32],{},[22,828,36],{},[22,830,149],{},[22,832,153],{},[22,834,157],{},[22,836,161],{},[14,838,165],{},[167,840,841,845,849,859],{},[170,842,843,175],{},[17,844,174],{},[170,846,847,181],{},[17,848,180],{},[170,850,851,187,853,29,855,33,857,197],{},[17,852,186],{},[22,854,190],{},[22,856,193],{},[22,858,196],{},[170,860,861,203],{},[17,862,202],{},[52,864,207],{"id":206},[14,866,210],{},[212,868,869],{"className":214,"code":215,"language":216,"meta":217,"style":217},[22,870,871,875,885,895,903],{"__ignoreMap":217},[221,872,873],{"class":223,"line":224},[221,874,228],{"class":227},[221,876,877,879,881,883],{"class":223,"line":231},[221,878,235],{"class":234},[221,880,238],{"class":227},[221,882,242],{"class":241},[221,884,245],{"class":227},[221,886,887,889,891,893],{"class":223,"line":248},[221,888,251],{"class":234},[221,890,238],{"class":227},[221,892,256],{"class":241},[221,894,245],{"class":227},[221,896,897,899,901],{"class":223,"line":261},[221,898,264],{"class":234},[221,900,238],{"class":227},[221,902,269],{"class":241},[221,904,905],{"class":223,"line":272},[221,906,275],{"class":227},[14,908,278,909,282,911,286,913,290,915,294],{},[22,910,281],{},[22,912,285],{},[22,914,289],{},[22,916,293],{},[52,918,298],{"id":297},[14,920,301,921,307],{},[46,922,306],{"href":304,"rel":923},[63],[212,925,926],{"className":310,"code":311,"language":312,"meta":217,"style":217},[22,927,928,932,936,940,944,948,952,956,960,964,968,972,976,980,984,988,992,996,1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1040,1044,1048,1052,1056],{"__ignoreMap":217},[221,929,930],{"class":223,"line":224},[221,931,319],{},[221,933,934],{"class":223,"line":231},[221,935,324],{},[221,937,938],{"class":223,"line":248},[221,939,329],{},[221,941,942],{"class":223,"line":261},[221,943,334],{},[221,945,946],{"class":223,"line":272},[221,947,339],{},[221,949,950],{"class":223,"line":342},[221,951,346],{"emptyLinePlaceholder":345},[221,953,954],{"class":223,"line":349},[221,955,352],{},[221,957,958],{"class":223,"line":355},[221,959,358],{},[221,961,962],{"class":223,"line":361},[221,963,364],{},[221,965,966],{"class":223,"line":367},[221,967,346],{"emptyLinePlaceholder":345},[221,969,970],{"class":223,"line":372},[221,971,375],{},[221,973,974],{"class":223,"line":378},[221,975,381],{},[221,977,978],{"class":223,"line":384},[221,979,346],{"emptyLinePlaceholder":345},[221,981,982],{"class":223,"line":389},[221,983,392],{},[221,985,986],{"class":223,"line":395},[221,987,398],{},[221,989,990],{"class":223,"line":401},[221,991,404],{},[221,993,994],{"class":223,"line":407},[221,995,410],{},[221,997,998],{"class":223,"line":413},[221,999,346],{"emptyLinePlaceholder":345},[221,1001,1002],{"class":223,"line":418},[221,1003,421],{},[221,1005,1006],{"class":223,"line":424},[221,1007,427],{},[221,1009,1010],{"class":223,"line":430},[221,1011,404],{},[221,1013,1014],{"class":223,"line":435},[221,1015,438],{},[221,1017,1018],{"class":223,"line":441},[221,1019,346],{"emptyLinePlaceholder":345},[221,1021,1022],{"class":223,"line":446},[221,1023,449],{},[221,1025,1026],{"class":223,"line":452},[221,1027,455],{},[221,1029,1030],{"class":223,"line":458},[221,1031,404],{},[221,1033,1034],{"class":223,"line":463},[221,1035,466],{},[221,1037,1038],{"class":223,"line":469},[221,1039,438],{},[221,1041,1042],{"class":223,"line":474},[221,1043,346],{"emptyLinePlaceholder":345},[221,1045,1046],{"class":223,"line":479},[221,1047,482],{},[221,1049,1050],{"class":223,"line":485},[221,1051,488],{},[221,1053,1054],{"class":223,"line":491},[221,1055,404],{},[221,1057,1058],{"class":223,"line":496},[221,1059,410],{},[14,1061,501],{},[167,1063,1064,1072,1084,1098],{},[170,1065,1066,509,1068,512,1070,516],{},[17,1067,508],{},[22,1069,28],{},[22,1071,515],{},[170,1073,1074,525,1078,528,1080,532,1082,536],{},[17,1075,1076,524],{},[22,1077,523],{},[22,1079,523],{},[22,1081,531],{},[22,1083,535],{},[170,1085,1086,544,1090,548,1092,552,1094,556,1096,559],{},[17,1087,541,1088,536],{},[22,1089,153],{},[22,1091,547],{},[22,1093,551],{},[22,1095,555],{},[22,1097,153],{},[170,1099,1100,565,1102,568],{},[17,1101,564],{},[22,1103,28],{},[52,1105,572],{"id":571},[574,1107,1108,1120,1126,1132],{},[170,1109,1110,581,1112,585,1114,589,1116,592,1118,596],{},[17,1111,580],{},[22,1113,584],{},[22,1115,588],{},[22,1117,24],{},[22,1119,595],{},[170,1121,1122,605],{},[17,1123,601,1124,536],{},[46,1125,604],{"href":48},[170,1127,1128,613],{},[17,1129,610,1130,536],{},[22,1131,293],{},[170,1133,1134,619,1136,624,1139,1159,652],{},[17,1135,618],{},[46,1137,623],{"href":61,"rel":1138},[63],[212,1140,1141],{"className":310,"code":627,"language":312,"meta":217,"style":217},[22,1142,1143,1147,1151,1155],{"__ignoreMap":217},[221,1144,1145],{"class":223,"line":224},[221,1146,634],{},[221,1148,1149],{"class":223,"line":231},[221,1150,639],{},[221,1152,1153],{"class":223,"line":248},[221,1154,634],{},[221,1156,1157],{"class":223,"line":261},[221,1158,648],{},[650,1160],{},[52,1162,656],{"id":655},[14,1164,659,1165,662,1167,665,1169,668,1171,672],{},[22,1166,588],{},[22,1168,24],{},[22,1170,595],{},[22,1172,671],{},[14,1174,675,1175,680],{},[46,1176,679],{"href":678},[682,1178,684],{},{"title":217,"searchDepth":231,"depth":231,"links":1180},[1181,1182,1183,1184,1185],{"id":54,"depth":231,"text":55},{"id":206,"depth":231,"text":207},{"id":297,"depth":231,"text":298},{"id":571,"depth":231,"text":572},{"id":655,"depth":231,"text":656},[1187,1188,1189,1190,1191],{"question":699,"answer":700},{"question":702,"answer":703},{"question":705,"answer":706},{"question":708,"answer":709},{"question":711,"answer":712},{"readingTimeStats":1193},{"text":716,"minutes":717,"time":718,"words":719},{"title":5,"description":694},[1196,1197,1198],{"title":726,"url":61},{"title":728,"url":304},{"title":730,"url":731},[735,736,737,738,739],{"id":1201,"title":1202,"author":1203,"body":1205,"category":692,"createdAt":1506,"description":1507,"disclosure":695,"extension":696,"faq":695,"location":713,"meta":1508,"navigation":345,"ogImage":695,"path":1513,"primaryKeyword":695,"readingTime":1514,"seo":1515,"sources":695,"stem":1516,"tags":1517,"takeaways":695,"updatedAt":1506,"url":1518,"__hash__":1519},"blog/blog/business-application-modernization-guide-2026.md","Business Application Modernization for Small Businesses (2026)",{"name":7,"social":1204},{"twitter":9},{"type":11,"value":1206,"toc":1498},[1207,1213,1217,1220,1252,1255,1259,1268,1350,1353,1357,1360,1376,1382,1385,1411,1415,1418,1444,1452,1456,1476,1480],[14,1208,1209,1212],{},[17,1210,1211],{},"Application modernization means updating software your business already depends on so it is cheaper to run, safer, and easier to change, without throwing away what works."," Most guides on the topic are written for large enterprises with mainframes and hundred-person IT teams. This one is for smaller companies: a custom app built a few years ago, an internal tool on an old framework, or a mobile app nobody wants to touch anymore.",[52,1214,1216],{"id":1215},"signs-your-app-needs-modernizing","Signs your app needs modernizing",[14,1218,1219],{},"You don't modernize because the code is old. You modernize because the age is starting to cost you. Common signs:",[167,1221,1222,1228,1234,1240,1246],{},[170,1223,1224,1227],{},[17,1225,1226],{},"Updates are risky."," Small changes break unrelated parts, so every release needs days of manual testing, or releases simply stop.",[170,1229,1230,1233],{},[17,1231,1232],{},"The platform is moving on without you."," The framework, language version or hosting is out of support, so security patches have stopped. App stores also raise their minimum requirements over time, and an app that can't be rebuilt with current tools eventually can't ship updates.",[170,1235,1236,1239],{},[17,1237,1238],{},"Only one person understands it."," If the original developer leaves, nobody can safely change it.",[170,1241,1242,1245],{},[17,1243,1244],{},"It doesn't fit how you work now."," Staff work around the app with spreadsheets, copy data between systems by hand, or can't use it on a phone.",[170,1247,1248,1251],{},[17,1249,1250],{},"Hosting or licence costs keep rising"," for a system that isn't doing more than it used to.",[14,1253,1254],{},"One sign alone is not a reason to rebuild. Two or three together usually are a reason to plan.",[52,1256,1258],{"id":1257},"the-options-from-lightest-to-heaviest","The options, from lightest to heaviest",[14,1260,1261,1262,1267],{},"Modernization is a range of choices, not a single project. A common way to describe them, adapted from the \"R\" models cloud providers use (",[46,1263,1266],{"href":1264,"rel":1265},"https://docs.aws.amazon.com/prescriptive-guidance/latest/large-migration-guide/migration-strategies.html",[63],"AWS: migration strategies","):",[67,1269,1270,1283],{},[70,1271,1272],{},[73,1273,1274,1277,1280],{},[76,1275,1276],{},"Option",[76,1278,1279],{},"What changes",[76,1281,1282],{},"When it fits",[83,1284,1285,1298,1311,1324,1337],{},[73,1286,1287,1292,1295],{},[88,1288,1289],{},[17,1290,1291],{},"Rehost",[88,1293,1294],{},"The same app moves to new hosting, for example from an old server to a managed cloud service.",[88,1296,1297],{},"The code is fine; the server is the problem.",[73,1299,1300,1305,1308],{},[88,1301,1302],{},[17,1303,1304],{},"Replatform",[88,1306,1307],{},"Small changes to use managed services: a managed database, managed auth, file storage.",[88,1309,1310],{},"You want less server upkeep without touching features.",[73,1312,1313,1318,1321],{},[88,1314,1315],{},[17,1316,1317],{},"Refactor",[88,1319,1320],{},"The code is restructured and upgraded in place: framework upgrades, tests added, modules split.",[88,1322,1323],{},"The app's design is sound, but the code has decayed.",[73,1325,1326,1331,1334],{},[88,1327,1328],{},[17,1329,1330],{},"Rebuild",[88,1332,1333],{},"A new app replaces the old one, usually feature by feature.",[88,1335,1336],{},"The design no longer fits the business, or the stack is a dead end.",[73,1338,1339,1344,1347],{},[88,1340,1341],{},[17,1342,1343],{},"Replace",[88,1345,1346],{},"An off-the-shelf product replaces the custom app.",[88,1348,1349],{},"What the app does is no longer unique to your business.",[14,1351,1352],{},"The honest answer is often a mix: rehost now to stop the bleeding, refactor the parts that still fit, and rebuild only the parts that don't. Replacing with an off-the-shelf product is worth a serious look before any rebuild, because a custom app you don't need is the most expensive kind.",[52,1354,1356],{"id":1355},"how-to-modernize-without-stopping-the-business","How to modernize without stopping the business",[14,1358,1359],{},"The biggest risk in modernization is the \"big bang\" rewrite: a year of building a new system while the old one freezes, then a risky switch-over. Two habits avoid most of that risk.",[14,1361,1362,1365,1366,1369,1370,1375],{},[17,1363,1364],{},"Replace it in slices."," The usual pattern is often called the ",[129,1367,1368],{},"strangler fig",": put the new system alongside the old one and move one feature at a time, routing users to the new version as each piece is ready, until nothing is left in the old system (",[46,1371,1374],{"href":1372,"rel":1373},"https://martinfowler.com/bliki/StranglerFigApplication.html",[63],"Martin Fowler: Strangler Fig Application","). Each slice ships on its own, so value arrives early and a problem only affects one slice.",[14,1377,1378,1381],{},[17,1379,1380],{},"Treat the data as the real project."," Screens are easy to rebuild; years of customer records, orders and history are not. Map the old data model before writing new code, plan how records move (once, or synced during the transition), and test the migration on a copy of real data, not samples.",[14,1383,1384],{},"A few more habits that pay off:",[167,1386,1387,1393,1399,1405],{},[170,1388,1389,1392],{},[17,1390,1391],{},"Write down what the old app actually does,"," including the odd rules nobody remembers until they break.",[170,1394,1395,1398],{},[17,1396,1397],{},"Add tests around the old behavior first,"," so you can prove the new version matches.",[170,1400,1401,1404],{},[17,1402,1403],{},"Keep both systems running"," until the new one has handled real use for a while.",[170,1406,1407,1410],{},[17,1408,1409],{},"Change the business process only when you mean to."," A modernization that also changes how people work needs training time and buy-in, so plan for both.",[52,1412,1414],{"id":1413},"what-a-modernized-stack-often-looks-like-for-a-small-business","What a modernized stack often looks like for a small business",[14,1416,1417],{},"There is no single right stack, but for a typical small-business app a modern setup tends to share these traits:",[167,1419,1420,1426,1432,1438],{},[170,1421,1422,1425],{},[17,1423,1424],{},"Managed services instead of servers you patch yourself:"," for example Firebase or Google Cloud for authentication, the database, file storage and server functions.",[170,1427,1428,1431],{},[17,1429,1430],{},"One codebase for mobile and web where it makes sense."," Flutter, for instance, builds iOS, Android and web apps from one Dart codebase, which cuts the number of apps to maintain.",[170,1433,1434,1437],{},[17,1435,1436],{},"Automated tests and a repeatable deploy,"," so updates stop being events.",[170,1439,1440,1443],{},[17,1441,1442],{},"Integrations through APIs"," (payments, email, calendars, accounting) instead of manual exports.",[14,1445,1446,1447,1451],{},"This is the stack we use for our own product, ",[46,1448,1450],{"href":1449},"/case-studies/workspace369/","Workspace369",", and the case study explains the trade-offs we made, including splitting a large backend into smaller, separately deployed parts.",[52,1453,1455],{"id":1454},"a-short-checklist-before-you-start","A short checklist before you start",[574,1457,1458,1461,1464,1467,1470,1473],{},[170,1459,1460],{},"List what the app does today and who uses each part.",[170,1462,1463],{},"Decide what must not change (data, integrations, URLs, compliance needs).",[170,1465,1466],{},"Choose an option per part: rehost, replatform, refactor, rebuild or replace.",[170,1468,1469],{},"Pick the first slice: small, valuable and low-risk.",[170,1471,1472],{},"Plan the data migration and test it on a copy of real data.",[170,1474,1475],{},"Set a date to switch off the old system, and keep it once the new one has proven itself.",[52,1477,1479],{"id":1478},"getting-help","Getting help",[14,1481,1482,1483,1487,1488,1492,1493,1497],{},"App369 helps small and mid-size businesses in Palm Beach County and beyond modernize apps they already rely on: upgrades, rebuilds in Flutter, and moving backends to managed cloud services. For ongoing upkeep after the work, see ",[46,1484,1486],{"href":1485},"/service/ongoing-maintenance/","software maintenance and support",". For a rough budget, try the ",[46,1489,1491],{"href":1490},"/tools/app-cost-calculator/","app cost calculator",", or ",[46,1494,1496],{"href":1495},"/contact/","send us your brief"," and we'll reply by email, usually within one business day.",{"title":217,"searchDepth":231,"depth":231,"links":1499},[1500,1501,1502,1503,1504,1505],{"id":1215,"depth":231,"text":1216},{"id":1257,"depth":231,"text":1258},{"id":1355,"depth":231,"text":1356},{"id":1413,"depth":231,"text":1414},{"id":1454,"depth":231,"text":1455},{"id":1478,"depth":231,"text":1479},"2026-10-12T19:00:00.000Z","Business application modernization for small and mid-size companies: how to tell an app needs it, the options from rehosting to a full rebuild, and how to modernize without stopping the business.",{"readingTimeStats":1509},{"text":716,"minutes":1510,"time":1511,"words":1512},4.59,275400,918,"/blog/business-application-modernization-guide-2026","8 min",{"title":1202,"description":1507},"blog/business-application-modernization-guide-2026","business application modernization, application modernization, legacy app modernization, app rebuild, refactor vs rebuild, software modernization","https://app369.com/blog/business-application-modernization-guide-2026/","Z29YAITIQIj67JdNWhv-hYIvcJIlrhkHEz37rl7MBpU",[1521],{"keyword":5,"url":740,"title":5,"relevance":1522},65,1792107377526]