[{"data":1,"prerenderedAt":2040},["ShallowReactive",2],{"/blog/how-to-test-firebase-security-rules-2026/":3,"/blog/how-to-test-firebase-security-rules-2026/-related":1230,"/blog/how-to-test-firebase-security-rules-2026/-resources":2022},{"id":4,"title":5,"author":6,"body":10,"category":1210,"createdAt":1211,"description":1212,"extension":1213,"location":1214,"meta":1215,"navigation":320,"ogImage":1221,"path":1222,"readingTime":1223,"seo":1224,"stem":1225,"tags":1226,"updatedAt":1227,"url":1228,"__hash__":1229},"blog/blog/how-to-test-firebase-security-rules-2026.md","How to Test Firebase Security Rules with the Emulator (2026)",{"name":7,"social":8},"Simon Dziak",{"twitter":9},"@SzymonDziak",{"type":11,"value":12,"toc":1194},"minimark",[13,26,40,45,55,95,123,127,149,166,212,216,219,227,230,940,946,950,953,1023,1026,1030,1042,1062,1072,1076,1087,1091,1099,1106,1110,1115,1137,1141,1154,1158,1168,1172,1183,1187,1190],[14,15,16,20,21,25],"p",{},[17,18,19],"strong",{},"The only reliable way to know your Firebase Security Rules work is to try to break them, automatically, every time they change."," Reading rules and clicking around the app misses the cases that matter: another user's data, another tenant's data, and a user giving themselves a higher role. The Firebase Emulator Suite and the ",[22,23,24],"code",{},"@firebase/rules-unit-testing"," library let you write those attempts as ordinary unit tests that run in seconds and never touch production.",[14,27,28,29,34,35,39],{},"This is the technique behind App369's ",[30,31,33],"a",{"href":32},"/fixed-price/firebase-security-rules-audit/","fixed-price Firebase Security Rules Audit",", and the kind of test Simon relies on for the rules in Workspace369, his production Flutter and Firebase SaaS. If you want the rules themselves explained first, start with the ",[30,36,38],{"href":37},"/blog/firebase-security-guide-2026/","Firebase Security Rules guide",".",[41,42,44],"h2",{"id":43},"step-1-install-and-start-the-emulators","Step 1: Install and start the emulators",[14,46,47,48,54],{},"You need Node.js, a recent Firebase CLI and a Java runtime for the Firestore emulator. The ",[30,49,53],{"href":50,"rel":51},"https://firebase.google.com/docs/emulator-suite/install_and_configure",[52],"nofollow","install and configure docs"," list the exact minimum versions. Then, in your project:",[56,57,62],"pre",{"className":58,"code":59,"language":60,"meta":61,"style":61},"language-bash shiki shiki-themes github-light github-dark","firebase init emulators\nfirebase emulators:start --only firestore\n","bash","",[22,63,64,80],{"__ignoreMap":61},[65,66,69,73,77],"span",{"class":67,"line":68},"line",1,[65,70,72],{"class":71},"sScJk","firebase",[65,74,76],{"class":75},"sZZnC"," init",[65,78,79],{"class":75}," emulators\n",[65,81,83,85,88,92],{"class":67,"line":82},2,[65,84,72],{"class":71},[65,86,87],{"class":75}," emulators:start",[65,89,91],{"class":90},"sj4cs"," --only",[65,93,94],{"class":75}," firestore\n",[14,96,97,100,101,104,105,108,109,112,113,118,119,122],{},[22,98,99],{},"firebase init emulators"," lets you pick emulators and downloads their binaries. By default the Firestore emulator listens on port 8080 and the Emulator UI on port 4000. Make sure ",[22,102,103],{},"firebase.json"," points at your rules file, for example ",[22,106,107],{},"\"firestore\": { \"rules\": \"firestore.rules\" }",", plus an ",[22,110,111],{},"emulators"," block if you want non-default ports. The ",[30,114,117],{"href":115,"rel":116},"https://firebase.google.com/docs/emulator-suite/connect_firestore",[52],"Firestore emulator docs"," also recommend demo project IDs (prefixed ",[22,120,121],{},"demo-","): code can't accidentally reach production resources, and no real Firebase project is needed.",[41,124,126],{"id":125},"step-2-set-up-the-test-library","Step 2: Set up the test library",[56,128,130],{"className":58,"code":129,"language":60,"meta":61,"style":61},"npm install --save-dev @firebase/rules-unit-testing firebase\n",[22,131,132],{"__ignoreMap":61},[65,133,134,137,140,143,146],{"class":67,"line":68},[65,135,136],{"class":71},"npm",[65,138,139],{"class":75}," install",[65,141,142],{"class":90}," --save-dev",[65,144,145],{"class":75}," @firebase/rules-unit-testing",[65,147,148],{"class":75}," firebase\n",[14,150,151,152,155,156,159,160,165],{},"The current library (v3) pairs with the modular Firebase JS SDK (v9 and later). You test through ",[22,153,154],{},"RulesTestEnvironment",", created with ",[22,157,158],{},"initializeTestEnvironment",". The ",[30,161,164],{"href":162,"rel":163},"https://firebase.google.com/docs/rules/unit-tests",[52],"official unit-test guide"," documents these pieces:",[167,168,169,176,186,192,202],"ul",{},[170,171,172,175],"li",{},[22,173,174],{},"initializeTestEnvironment({ projectId, firestore: { rules } })"," creates the environment from your rules text.",[170,177,178,181,182,185],{},[22,179,180],{},"testEnv.authenticatedContext(uid, tokenOptions)"," acts as a signed-in user; ",[22,183,184],{},"testEnv.unauthenticatedContext()"," acts as a signed-out client.",[170,187,188,191],{},[22,189,190],{},"testEnv.withSecurityRulesDisabled(callback)"," runs setup code that bypasses rules, so you can seed data.",[170,193,194,197,198,201],{},[22,195,196],{},"assertSucceeds(promise)"," and ",[22,199,200],{},"assertFails(promise)"," check whether rules allowed or denied an operation.",[170,203,204,207,208,211],{},[22,205,206],{},"testEnv.clearFirestore()"," clears data between tests, and ",[22,209,210],{},"testEnv.cleanup()"," releases resources when you finish.",[41,213,215],{"id":214},"step-3-write-a-first-test-file","Step 3: Write a first test file",[14,217,218],{},"Say your rules let a user read their own profile, create it only as a plain member, and never change their own role:",[56,220,225],{"className":221,"code":223,"language":224},[222],"language-text","rules_version = '2';\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /users/{uid} {\n      allow read: if request.auth != null && request.auth.uid == uid;\n      allow create: if request.auth != null && request.auth.uid == uid\n        && request.resource.data.role == 'member';\n      allow update: if request.auth != null && request.auth.uid == uid\n        && request.resource.data.role == resource.data.role;\n    }\n  }\n}\n","text",[22,226,223],{"__ignoreMap":61},[14,228,229],{},"A Jest-style test file in TypeScript:",[56,231,235],{"className":232,"code":233,"language":234,"meta":61,"style":61},"language-ts shiki shiki-themes github-light github-dark","import { readFileSync } from 'fs';\nimport {\n  initializeTestEnvironment,\n  assertFails,\n  assertSucceeds,\n  RulesTestEnvironment,\n} from '@firebase/rules-unit-testing';\nimport { doc, getDoc, setDoc, updateDoc } from 'firebase/firestore';\n\nlet testEnv: RulesTestEnvironment;\n\nbeforeAll(async () => {\n  testEnv = await initializeTestEnvironment({\n    projectId: 'demo-rules-test',\n    firestore: {\n      rules: readFileSync('firestore.rules', 'utf8'),\n      host: '127.0.0.1',\n      port: 8080,\n    },\n  });\n});\n\nafterAll(async () => {\n  await testEnv.cleanup();\n});\n\nbeforeEach(async () => {\n  await testEnv.clearFirestore();\n  // Seed data with rules switched off\n  await testEnv.withSecurityRulesDisabled(async (ctx) => {\n    await setDoc(doc(ctx.firestore(), 'users/alice'), { role: 'member' });\n  });\n});\n\ntest('a user can read their own profile', async () => {\n  const alice = testEnv.authenticatedContext('alice');\n  await assertSucceeds(getDoc(doc(alice.firestore(), 'users/alice')));\n});\n\ntest('a user cannot read someone else\\'s profile', async () => {\n  const bob = testEnv.authenticatedContext('bob');\n  await assertFails(getDoc(doc(bob.firestore(), 'users/alice')));\n});\n\ntest('a signed-out client cannot read anything', async () => {\n  const anon = testEnv.unauthenticatedContext();\n  await assertFails(getDoc(doc(anon.firestore(), 'users/alice')));\n});\n\ntest('a user cannot promote themselves to admin', async () => {\n  const alice = testEnv.authenticatedContext('alice');\n  await assertFails(updateDoc(doc(alice.firestore(), 'users/alice'), { role: 'admin' }));\n});\n","ts",[22,236,237,256,263,269,275,281,287,300,315,322,339,344,364,382,394,400,423,434,445,451,457,463,468,484,499,504,509,525,537,544,572,607,612,617,622,643,668,697,702,707,733,754,781,786,791,811,828,854,859,864,884,903,935],{"__ignoreMap":61},[65,238,239,243,247,250,253],{"class":67,"line":68},[65,240,242],{"class":241},"szBVR","import",[65,244,246],{"class":245},"sVt8B"," { readFileSync } ",[65,248,249],{"class":241},"from",[65,251,252],{"class":75}," 'fs'",[65,254,255],{"class":245},";\n",[65,257,258,260],{"class":67,"line":82},[65,259,242],{"class":241},[65,261,262],{"class":245}," {\n",[65,264,266],{"class":67,"line":265},3,[65,267,268],{"class":245},"  initializeTestEnvironment,\n",[65,270,272],{"class":67,"line":271},4,[65,273,274],{"class":245},"  assertFails,\n",[65,276,278],{"class":67,"line":277},5,[65,279,280],{"class":245},"  assertSucceeds,\n",[65,282,284],{"class":67,"line":283},6,[65,285,286],{"class":245},"  RulesTestEnvironment,\n",[65,288,290,293,295,298],{"class":67,"line":289},7,[65,291,292],{"class":245},"} ",[65,294,249],{"class":241},[65,296,297],{"class":75}," '@firebase/rules-unit-testing'",[65,299,255],{"class":245},[65,301,303,305,308,310,313],{"class":67,"line":302},8,[65,304,242],{"class":241},[65,306,307],{"class":245}," { doc, getDoc, setDoc, updateDoc } ",[65,309,249],{"class":241},[65,311,312],{"class":75}," 'firebase/firestore'",[65,314,255],{"class":245},[65,316,318],{"class":67,"line":317},9,[65,319,321],{"emptyLinePlaceholder":320},true,"\n",[65,323,325,328,331,334,337],{"class":67,"line":324},10,[65,326,327],{"class":241},"let",[65,329,330],{"class":245}," testEnv",[65,332,333],{"class":241},":",[65,335,336],{"class":71}," RulesTestEnvironment",[65,338,255],{"class":245},[65,340,342],{"class":67,"line":341},11,[65,343,321],{"emptyLinePlaceholder":320},[65,345,347,350,353,356,359,362],{"class":67,"line":346},12,[65,348,349],{"class":71},"beforeAll",[65,351,352],{"class":245},"(",[65,354,355],{"class":241},"async",[65,357,358],{"class":245}," () ",[65,360,361],{"class":241},"=>",[65,363,262],{"class":245},[65,365,367,370,373,376,379],{"class":67,"line":366},13,[65,368,369],{"class":245},"  testEnv ",[65,371,372],{"class":241},"=",[65,374,375],{"class":241}," await",[65,377,378],{"class":71}," initializeTestEnvironment",[65,380,381],{"class":245},"({\n",[65,383,385,388,391],{"class":67,"line":384},14,[65,386,387],{"class":245},"    projectId: ",[65,389,390],{"class":75},"'demo-rules-test'",[65,392,393],{"class":245},",\n",[65,395,397],{"class":67,"line":396},15,[65,398,399],{"class":245},"    firestore: {\n",[65,401,403,406,409,411,414,417,420],{"class":67,"line":402},16,[65,404,405],{"class":245},"      rules: ",[65,407,408],{"class":71},"readFileSync",[65,410,352],{"class":245},[65,412,413],{"class":75},"'firestore.rules'",[65,415,416],{"class":245},", ",[65,418,419],{"class":75},"'utf8'",[65,421,422],{"class":245},"),\n",[65,424,426,429,432],{"class":67,"line":425},17,[65,427,428],{"class":245},"      host: ",[65,430,431],{"class":75},"'127.0.0.1'",[65,433,393],{"class":245},[65,435,437,440,443],{"class":67,"line":436},18,[65,438,439],{"class":245},"      port: ",[65,441,442],{"class":90},"8080",[65,444,393],{"class":245},[65,446,448],{"class":67,"line":447},19,[65,449,450],{"class":245},"    },\n",[65,452,454],{"class":67,"line":453},20,[65,455,456],{"class":245},"  });\n",[65,458,460],{"class":67,"line":459},21,[65,461,462],{"class":245},"});\n",[65,464,466],{"class":67,"line":465},22,[65,467,321],{"emptyLinePlaceholder":320},[65,469,471,474,476,478,480,482],{"class":67,"line":470},23,[65,472,473],{"class":71},"afterAll",[65,475,352],{"class":245},[65,477,355],{"class":241},[65,479,358],{"class":245},[65,481,361],{"class":241},[65,483,262],{"class":245},[65,485,487,490,493,496],{"class":67,"line":486},24,[65,488,489],{"class":241},"  await",[65,491,492],{"class":245}," testEnv.",[65,494,495],{"class":71},"cleanup",[65,497,498],{"class":245},"();\n",[65,500,502],{"class":67,"line":501},25,[65,503,462],{"class":245},[65,505,507],{"class":67,"line":506},26,[65,508,321],{"emptyLinePlaceholder":320},[65,510,512,515,517,519,521,523],{"class":67,"line":511},27,[65,513,514],{"class":71},"beforeEach",[65,516,352],{"class":245},[65,518,355],{"class":241},[65,520,358],{"class":245},[65,522,361],{"class":241},[65,524,262],{"class":245},[65,526,528,530,532,535],{"class":67,"line":527},28,[65,529,489],{"class":241},[65,531,492],{"class":245},[65,533,534],{"class":71},"clearFirestore",[65,536,498],{"class":245},[65,538,540],{"class":67,"line":539},29,[65,541,543],{"class":542},"sJ8bj","  // Seed data with rules switched off\n",[65,545,547,549,551,554,556,558,561,565,568,570],{"class":67,"line":546},30,[65,548,489],{"class":241},[65,550,492],{"class":245},[65,552,553],{"class":71},"withSecurityRulesDisabled",[65,555,352],{"class":245},[65,557,355],{"class":241},[65,559,560],{"class":245}," (",[65,562,564],{"class":563},"s4XuR","ctx",[65,566,567],{"class":245},") ",[65,569,361],{"class":241},[65,571,262],{"class":245},[65,573,575,578,581,583,586,589,592,595,598,601,604],{"class":67,"line":574},31,[65,576,577],{"class":241},"    await",[65,579,580],{"class":71}," setDoc",[65,582,352],{"class":245},[65,584,585],{"class":71},"doc",[65,587,588],{"class":245},"(ctx.",[65,590,591],{"class":71},"firestore",[65,593,594],{"class":245},"(), ",[65,596,597],{"class":75},"'users/alice'",[65,599,600],{"class":245},"), { role: ",[65,602,603],{"class":75},"'member'",[65,605,606],{"class":245}," });\n",[65,608,610],{"class":67,"line":609},32,[65,611,456],{"class":245},[65,613,615],{"class":67,"line":614},33,[65,616,462],{"class":245},[65,618,620],{"class":67,"line":619},34,[65,621,321],{"emptyLinePlaceholder":320},[65,623,625,628,630,633,635,637,639,641],{"class":67,"line":624},35,[65,626,627],{"class":71},"test",[65,629,352],{"class":245},[65,631,632],{"class":75},"'a user can read their own profile'",[65,634,416],{"class":245},[65,636,355],{"class":241},[65,638,358],{"class":245},[65,640,361],{"class":241},[65,642,262],{"class":245},[65,644,646,649,652,655,657,660,662,665],{"class":67,"line":645},36,[65,647,648],{"class":241},"  const",[65,650,651],{"class":90}," alice",[65,653,654],{"class":241}," =",[65,656,492],{"class":245},[65,658,659],{"class":71},"authenticatedContext",[65,661,352],{"class":245},[65,663,664],{"class":75},"'alice'",[65,666,667],{"class":245},");\n",[65,669,671,673,676,678,681,683,685,688,690,692,694],{"class":67,"line":670},37,[65,672,489],{"class":241},[65,674,675],{"class":71}," assertSucceeds",[65,677,352],{"class":245},[65,679,680],{"class":71},"getDoc",[65,682,352],{"class":245},[65,684,585],{"class":71},[65,686,687],{"class":245},"(alice.",[65,689,591],{"class":71},[65,691,594],{"class":245},[65,693,597],{"class":75},[65,695,696],{"class":245},")));\n",[65,698,700],{"class":67,"line":699},38,[65,701,462],{"class":245},[65,703,705],{"class":67,"line":704},39,[65,706,321],{"emptyLinePlaceholder":320},[65,708,710,712,714,717,720,723,725,727,729,731],{"class":67,"line":709},40,[65,711,627],{"class":71},[65,713,352],{"class":245},[65,715,716],{"class":75},"'a user cannot read someone else",[65,718,719],{"class":90},"\\'",[65,721,722],{"class":75},"s profile'",[65,724,416],{"class":245},[65,726,355],{"class":241},[65,728,358],{"class":245},[65,730,361],{"class":241},[65,732,262],{"class":245},[65,734,736,738,741,743,745,747,749,752],{"class":67,"line":735},41,[65,737,648],{"class":241},[65,739,740],{"class":90}," bob",[65,742,654],{"class":241},[65,744,492],{"class":245},[65,746,659],{"class":71},[65,748,352],{"class":245},[65,750,751],{"class":75},"'bob'",[65,753,667],{"class":245},[65,755,757,759,762,764,766,768,770,773,775,777,779],{"class":67,"line":756},42,[65,758,489],{"class":241},[65,760,761],{"class":71}," assertFails",[65,763,352],{"class":245},[65,765,680],{"class":71},[65,767,352],{"class":245},[65,769,585],{"class":71},[65,771,772],{"class":245},"(bob.",[65,774,591],{"class":71},[65,776,594],{"class":245},[65,778,597],{"class":75},[65,780,696],{"class":245},[65,782,784],{"class":67,"line":783},43,[65,785,462],{"class":245},[65,787,789],{"class":67,"line":788},44,[65,790,321],{"emptyLinePlaceholder":320},[65,792,794,796,798,801,803,805,807,809],{"class":67,"line":793},45,[65,795,627],{"class":71},[65,797,352],{"class":245},[65,799,800],{"class":75},"'a signed-out client cannot read anything'",[65,802,416],{"class":245},[65,804,355],{"class":241},[65,806,358],{"class":245},[65,808,361],{"class":241},[65,810,262],{"class":245},[65,812,814,816,819,821,823,826],{"class":67,"line":813},46,[65,815,648],{"class":241},[65,817,818],{"class":90}," anon",[65,820,654],{"class":241},[65,822,492],{"class":245},[65,824,825],{"class":71},"unauthenticatedContext",[65,827,498],{"class":245},[65,829,831,833,835,837,839,841,843,846,848,850,852],{"class":67,"line":830},47,[65,832,489],{"class":241},[65,834,761],{"class":71},[65,836,352],{"class":245},[65,838,680],{"class":71},[65,840,352],{"class":245},[65,842,585],{"class":71},[65,844,845],{"class":245},"(anon.",[65,847,591],{"class":71},[65,849,594],{"class":245},[65,851,597],{"class":75},[65,853,696],{"class":245},[65,855,857],{"class":67,"line":856},48,[65,858,462],{"class":245},[65,860,862],{"class":67,"line":861},49,[65,863,321],{"emptyLinePlaceholder":320},[65,865,867,869,871,874,876,878,880,882],{"class":67,"line":866},50,[65,868,627],{"class":71},[65,870,352],{"class":245},[65,872,873],{"class":75},"'a user cannot promote themselves to admin'",[65,875,416],{"class":245},[65,877,355],{"class":241},[65,879,358],{"class":245},[65,881,361],{"class":241},[65,883,262],{"class":245},[65,885,887,889,891,893,895,897,899,901],{"class":67,"line":886},51,[65,888,648],{"class":241},[65,890,651],{"class":90},[65,892,654],{"class":241},[65,894,492],{"class":245},[65,896,659],{"class":71},[65,898,352],{"class":245},[65,900,664],{"class":75},[65,902,667],{"class":245},[65,904,906,908,910,912,915,917,919,921,923,925,927,929,932],{"class":67,"line":905},52,[65,907,489],{"class":241},[65,909,761],{"class":71},[65,911,352],{"class":245},[65,913,914],{"class":71},"updateDoc",[65,916,352],{"class":245},[65,918,585],{"class":71},[65,920,687],{"class":245},[65,922,591],{"class":71},[65,924,594],{"class":245},[65,926,597],{"class":75},[65,928,600],{"class":245},[65,930,931],{"class":75},"'admin'",[65,933,934],{"class":245}," }));\n",[65,936,938],{"class":67,"line":937},53,[65,939,462],{"class":245},[14,941,942,943,945],{},"Run it while the emulator is up, with your usual test runner. The seed step matters: tests that depend on existing documents must create them with ",[22,944,553],{},", otherwise your own rules block the setup.",[41,947,949],{"id":948},"step-4-decide-what-to-test","Step 4: Decide what to test",[14,951,952],{},"Passing tests for the happy path prove little. Write the attacks:",[954,955,956,962,968,985,991,1000,1017],"ol",{},[170,957,958,961],{},[17,959,960],{},"Cross-user reads and writes."," User B reading, updating and deleting user A's documents, in every collection that holds user data.",[170,963,964,967],{},[17,965,966],{},"Cross-tenant access."," If you have workspaces, teams or organizations, a member of tenant 1 must fail to read or write tenant 2's documents, including sub-collections. Seed two tenants and test both directions.",[170,969,970,973,974,416,977,980,981,984],{},[17,971,972],{},"Role escalation."," A user writing their own ",[22,975,976],{},"role",[22,978,979],{},"isAdmin"," or ",[22,982,983],{},"plan"," field on create and on update. Also test adding themselves to another tenant's member list.",[170,986,987,990],{},[17,988,989],{},"Field-level validation."," Creates with missing or extra fields, wrong types, oversized strings, and fields the client should never set, such as server-owned timestamps or counters.",[170,992,993,996,997,39],{},[17,994,995],{},"Signed-out access."," Every path with ",[22,998,999],{},"unauthenticatedContext()",[170,1001,1002,1005,1006,1009,1010,1012,1013,1016],{},[17,1003,1004],{},"Storage paths."," The same user and tenant checks against Storage rules. Pass ",[22,1007,1008],{},"storage: { rules }"," to ",[22,1011,158],{}," and use ",[22,1014,1015],{},"ctx.storage()"," in the tests.",[170,1018,1019,1022],{},[17,1020,1021],{},"Queries, not just single reads."," A rule that allows reading one document can still reject a list query that isn't constrained to what the rule permits. Test the exact query your app runs.",[14,1024,1025],{},"For each tenant-isolation rule, write one test that should succeed and at least one that should fail. If you can't make a failing test fail, you have found a hole.",[41,1027,1029],{"id":1028},"step-5-run-it-in-ci","Step 5: Run it in CI",[14,1031,1032,1035,1036,1041],{},[22,1033,1034],{},"firebase emulators:exec"," starts the emulators, runs your command, and shuts them down when the command ends. The ",[30,1037,1040],{"href":1038,"rel":1039},"https://firebase.google.com/docs/emulator-suite/connect_and_prototype",[52],"emulator docs"," describe it as the way to run scripted tests, and it suits continuous integration:",[56,1043,1045],{"className":58,"code":1044,"language":60,"meta":61,"style":61},"firebase emulators:exec --only firestore \"npm test\"\n",[22,1046,1047],{"__ignoreMap":61},[65,1048,1049,1051,1054,1056,1059],{"class":67,"line":68},[65,1050,72],{"class":71},[65,1052,1053],{"class":75}," emulators:exec",[65,1055,91],{"class":90},[65,1057,1058],{"class":75}," firestore",[65,1060,1061],{"class":75}," \"npm test\"\n",[14,1063,1064,1065,416,1068,1071],{},"The Firestore emulator clears its data when it shuts down, so every CI run starts clean. Cache the emulator download between CI runs if your provider supports it, and add the Java runtime to the CI image. Make the rules test a required check for any pull request that touches ",[22,1066,1067],{},"firestore.rules",[22,1069,1070],{},"storage.rules"," or the tests.",[41,1073,1075],{"id":1074},"step-6-debug-with-the-request-monitor","Step 6: Debug with the request monitor",[14,1077,1078,1079,1082,1083,1086],{},"When a test fails and you don't know which rule caused it, run ",[22,1080,1081],{},"firebase emulators:start",", point a manual client at it, and open the Emulator UI at ",[22,1084,1085],{},"http://localhost:4000",". In the Firestore section, the Requests tab shows each request and traces how your rules were evaluated, which line allowed or denied it. The Firestore emulator docs describe this request monitoring. It is the fastest way to find out that a rule you thought applied never matched.",[41,1088,1090],{"id":1089},"when-to-get-help","When to get help",[14,1092,1093,1094,1098],{},"Before you write tests, a quick first pass helps: paste your rules into the free ",[30,1095,1097],{"href":1096},"/tools/firebase-rules-checker/","Firebase Security Rules checker"," to spot public paths, test-mode rules and sign-in-only checks. It runs in your browser.",[14,1100,1101,1102,1105],{},"If you don't have the time to build the model of who should access what, or you have already found a hole and want the fixes reviewed, App369's ",[30,1103,1104],{"href":32},"fixed-price Firebase Security Rules Audit + Fix PR"," is $950 and delivered in 5 business days. It covers Firestore and Storage rules plus up to 15 Cloud Functions in one Firebase project, and you get a written role and tenant access model, an emulator test suite for cross-user, cross-tenant and role-escalation attempts, a severity-ranked report and a pull request with fixes that passes the suite. It runs on the emulator or test data only, and it is a technical review, not a compliance certificate.",[41,1107,1109],{"id":1108},"frequently-asked-questions","Frequently Asked Questions",[1111,1112,1114],"h3",{"id":1113},"how-do-i-test-firestore-security-rules-locally","How do I test Firestore security rules locally?",[14,1116,1117,1118,1121,1122,1124,1125,1127,1128,1130,1131,197,1134,39],{},"Run the Firestore emulator with ",[22,1119,1120],{},"firebase emulators:start --only firestore",", then write tests with ",[22,1123,24],{},". Create a test environment with ",[22,1126,158],{},", act as users with ",[22,1129,659],{},", and check results with ",[22,1132,1133],{},"assertSucceeds",[22,1135,1136],{},"assertFails",[1111,1138,1140],{"id":1139},"can-i-test-firebase-storage-rules-the-same-way","Can I test Firebase Storage rules the same way?",[14,1142,1143,1144,1146,1147,1149,1150,1153],{},"Yes. Pass a ",[22,1145,1008],{}," option to ",[22,1148,158],{},", start the Storage emulator, and perform uploads and downloads through the context's ",[22,1151,1152],{},"storage()"," client. The same assertions apply.",[1111,1155,1157],{"id":1156},"how-do-i-run-firebase-rules-tests-in-ci","How do I run Firebase rules tests in CI?",[14,1159,1160,1161,1163,1164,1167],{},"Use ",[22,1162,1034],{}," to start the emulators, run your test command and stop them. For example, ",[22,1165,1166],{},"firebase emulators:exec --only firestore \"npm test\"",". The CI machine needs Node.js, the Firebase CLI and a Java runtime.",[1111,1169,1171],{"id":1170},"how-do-i-add-test-data-when-my-rules-would-block-it","How do I add test data when my rules would block it?",[14,1173,1174,1175,1178,1179,1182],{},"Call ",[22,1176,1177],{},"testEnv.withSecurityRulesDisabled(async (ctx) => { ... })"," and write documents through ",[22,1180,1181],{},"ctx.firestore()",". Rules are skipped inside that callback only.",[1111,1184,1186],{"id":1185},"does-passing-rules-tests-mean-my-app-is-secure","Does passing rules tests mean my app is secure?",[14,1188,1189],{},"No. Tests show your rules behave as you described them, so they are only as good as the cases you wrote. They don't cover Cloud Functions logic, leaked credentials or anything outside Firebase.",[1191,1192,1193],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":61,"searchDepth":82,"depth":82,"links":1195},[1196,1197,1198,1199,1200,1201,1202,1203],{"id":43,"depth":82,"text":44},{"id":125,"depth":82,"text":126},{"id":214,"depth":82,"text":215},{"id":948,"depth":82,"text":949},{"id":1028,"depth":82,"text":1029},{"id":1074,"depth":82,"text":1075},{"id":1089,"depth":82,"text":1090},{"id":1108,"depth":82,"text":1109,"children":1204},[1205,1206,1207,1208,1209],{"id":1113,"depth":265,"text":1114},{"id":1139,"depth":265,"text":1140},{"id":1156,"depth":265,"text":1157},{"id":1170,"depth":265,"text":1171},{"id":1185,"depth":265,"text":1186},"AI & Technology","2026-10-12T18:00:00.000Z","Unit test Firestore and Storage Security Rules with the Firebase Emulator Suite and @firebase/rules-unit-testing: setup, a TypeScript example, what to test, and CI.","md","Boynton Beach, FL",{"readingTimeStats":1216},{"text":1217,"minutes":1218,"time":1219,"words":1220},"7 min read",6.31,378600,1262,null,"/blog/how-to-test-firebase-security-rules-2026","7 min",{"title":5,"description":1212},"blog/how-to-test-firebase-security-rules-2026","firebase security rules testing, firestore rules unit test, firebase emulator, rules-unit-testing, firebase emulators exec, firestore security, storage rules","2026-10-12T20:00:00.000Z","https://app369.com/blog/how-to-test-firebase-security-rules-2026/","SvGwKSb33v_ZqVxg1UgWv8f4YGwimc3mWJKq5HFbi2U",[1231,1559],{"id":1232,"title":1233,"author":1234,"body":1236,"category":1210,"createdAt":1544,"description":1545,"extension":1213,"location":1214,"meta":1546,"navigation":320,"ogImage":1221,"path":1552,"readingTime":1553,"seo":1554,"stem":1555,"tags":1556,"updatedAt":1544,"url":1557,"__hash__":1558},"blog/blog/business-application-modernization-guide-2026.md","Business Application Modernization for Small Businesses (2026)",{"name":7,"social":1235},{"twitter":9},{"type":11,"value":1237,"toc":1536},[1238,1244,1248,1251,1283,1286,1290,1299,1387,1390,1394,1397,1414,1420,1423,1449,1453,1456,1482,1490,1494,1514,1518],[14,1239,1240,1243],{},[17,1241,1242],{},"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.",[41,1245,1247],{"id":1246},"signs-your-app-needs-modernizing","Signs your app needs modernizing",[14,1249,1250],{},"You don't modernize because the code is old. You modernize because the age is starting to cost you. Common signs:",[167,1252,1253,1259,1265,1271,1277],{},[170,1254,1255,1258],{},[17,1256,1257],{},"Updates are risky."," Small changes break unrelated parts, so every release needs days of manual testing, or releases simply stop.",[170,1260,1261,1264],{},[17,1262,1263],{},"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,1266,1267,1270],{},[17,1268,1269],{},"Only one person understands it."," If the original developer leaves, nobody can safely change it.",[170,1272,1273,1276],{},[17,1274,1275],{},"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,1278,1279,1282],{},[17,1280,1281],{},"Hosting or licence costs keep rising"," for a system that isn't doing more than it used to.",[14,1284,1285],{},"One sign alone is not a reason to rebuild. Two or three together usually are a reason to plan.",[41,1287,1289],{"id":1288},"the-options-from-lightest-to-heaviest","The options, from lightest to heaviest",[14,1291,1292,1293,1298],{},"Modernization is a range of choices, not a single project. A common way to describe them, adapted from the \"R\" models cloud providers use (",[30,1294,1297],{"href":1295,"rel":1296},"https://docs.aws.amazon.com/prescriptive-guidance/latest/large-migration-guide/migration-strategies.html",[52],"AWS: migration strategies","):",[1300,1301,1302,1318],"table",{},[1303,1304,1305],"thead",{},[1306,1307,1308,1312,1315],"tr",{},[1309,1310,1311],"th",{},"Option",[1309,1313,1314],{},"What changes",[1309,1316,1317],{},"When it fits",[1319,1320,1321,1335,1348,1361,1374],"tbody",{},[1306,1322,1323,1329,1332],{},[1324,1325,1326],"td",{},[17,1327,1328],{},"Rehost",[1324,1330,1331],{},"The same app moves to new hosting, for example from an old server to a managed cloud service.",[1324,1333,1334],{},"The code is fine; the server is the problem.",[1306,1336,1337,1342,1345],{},[1324,1338,1339],{},[17,1340,1341],{},"Replatform",[1324,1343,1344],{},"Small changes to use managed services: a managed database, managed auth, file storage.",[1324,1346,1347],{},"You want less server upkeep without touching features.",[1306,1349,1350,1355,1358],{},[1324,1351,1352],{},[17,1353,1354],{},"Refactor",[1324,1356,1357],{},"The code is restructured and upgraded in place: framework upgrades, tests added, modules split.",[1324,1359,1360],{},"The app's design is sound, but the code has decayed.",[1306,1362,1363,1368,1371],{},[1324,1364,1365],{},[17,1366,1367],{},"Rebuild",[1324,1369,1370],{},"A new app replaces the old one, usually feature by feature.",[1324,1372,1373],{},"The design no longer fits the business, or the stack is a dead end.",[1306,1375,1376,1381,1384],{},[1324,1377,1378],{},[17,1379,1380],{},"Replace",[1324,1382,1383],{},"An off-the-shelf product replaces the custom app.",[1324,1385,1386],{},"What the app does is no longer unique to your business.",[14,1388,1389],{},"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.",[41,1391,1393],{"id":1392},"how-to-modernize-without-stopping-the-business","How to modernize without stopping the business",[14,1395,1396],{},"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,1398,1399,1402,1403,1407,1408,1413],{},[17,1400,1401],{},"Replace it in slices."," The usual pattern is often called the ",[1404,1405,1406],"em",{},"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 (",[30,1409,1412],{"href":1410,"rel":1411},"https://martinfowler.com/bliki/StranglerFigApplication.html",[52],"Martin Fowler: Strangler Fig Application","). Each slice ships on its own, so value arrives early and a problem only affects one slice.",[14,1415,1416,1419],{},[17,1417,1418],{},"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,1421,1422],{},"A few more habits that pay off:",[167,1424,1425,1431,1437,1443],{},[170,1426,1427,1430],{},[17,1428,1429],{},"Write down what the old app actually does,"," including the odd rules nobody remembers until they break.",[170,1432,1433,1436],{},[17,1434,1435],{},"Add tests around the old behavior first,"," so you can prove the new version matches.",[170,1438,1439,1442],{},[17,1440,1441],{},"Keep both systems running"," until the new one has handled real use for a while.",[170,1444,1445,1448],{},[17,1446,1447],{},"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.",[41,1450,1452],{"id":1451},"what-a-modernized-stack-often-looks-like-for-a-small-business","What a modernized stack often looks like for a small business",[14,1454,1455],{},"There is no single right stack, but for a typical small-business app a modern setup tends to share these traits:",[167,1457,1458,1464,1470,1476],{},[170,1459,1460,1463],{},[17,1461,1462],{},"Managed services instead of servers you patch yourself:"," for example Firebase or Google Cloud for authentication, the database, file storage and server functions.",[170,1465,1466,1469],{},[17,1467,1468],{},"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,1471,1472,1475],{},[17,1473,1474],{},"Automated tests and a repeatable deploy,"," so updates stop being events.",[170,1477,1478,1481],{},[17,1479,1480],{},"Integrations through APIs"," (payments, email, calendars, accounting) instead of manual exports.",[14,1483,1484,1485,1489],{},"This is the stack we use for our own product, ",[30,1486,1488],{"href":1487},"/case-studies/workspace369/","Workspace369",", and the case study explains the trade-offs we made, including splitting a large backend into smaller, separately deployed parts.",[41,1491,1493],{"id":1492},"a-short-checklist-before-you-start","A short checklist before you start",[954,1495,1496,1499,1502,1505,1508,1511],{},[170,1497,1498],{},"List what the app does today and who uses each part.",[170,1500,1501],{},"Decide what must not change (data, integrations, URLs, compliance needs).",[170,1503,1504],{},"Choose an option per part: rehost, replatform, refactor, rebuild or replace.",[170,1506,1507],{},"Pick the first slice: small, valuable and low-risk.",[170,1509,1510],{},"Plan the data migration and test it on a copy of real data.",[170,1512,1513],{},"Set a date to switch off the old system, and keep it once the new one has proven itself.",[41,1515,1517],{"id":1516},"getting-help","Getting help",[14,1519,1520,1521,1525,1526,1530,1531,1535],{},"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 ",[30,1522,1524],{"href":1523},"/service/ongoing-maintenance/","software maintenance and support",". For a rough budget, try the ",[30,1527,1529],{"href":1528},"/tools/app-cost-calculator/","app cost calculator",", or ",[30,1532,1534],{"href":1533},"/contact/","send us your brief"," and we'll reply by email, usually within one business day.",{"title":61,"searchDepth":82,"depth":82,"links":1537},[1538,1539,1540,1541,1542,1543],{"id":1246,"depth":82,"text":1247},{"id":1288,"depth":82,"text":1289},{"id":1392,"depth":82,"text":1393},{"id":1451,"depth":82,"text":1452},{"id":1492,"depth":82,"text":1493},{"id":1516,"depth":82,"text":1517},"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":1547},{"text":1548,"minutes":1549,"time":1550,"words":1551},"5 min read",4.59,275400,918,"/blog/business-application-modernization-guide-2026","8 min",{"title":1233,"description":1545},"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/","KbwefmOfD_7L9xAKnESgFEWwa42dJhu7cQQdFs5BxH8",{"id":1560,"title":1561,"author":1562,"body":1564,"category":1210,"createdAt":1211,"description":2009,"extension":1213,"location":1214,"meta":2010,"navigation":320,"ogImage":1221,"path":2016,"readingTime":1223,"seo":2017,"stem":2018,"tags":2019,"updatedAt":1211,"url":2020,"__hash__":2021},"blog/blog/apps-built-with-flutter-examples-2026.md","Apps Built with Flutter: Real Examples by Industry (2026)",{"name":7,"social":1563},{"twitter":9},{"type":11,"value":1565,"toc":1998},[1566,1578,1581,1585,1644,1647,1651,1703,1706,1710,1769,1773,1815,1819,1871,1875,1934,1938,1944,1948,1979,1983],[14,1567,1568,1571,1572,1577],{},[17,1569,1570],{},"Flutter is behind apps from Google, BMW, Toyota, Nubank, Headspace, eBay and MGM Resorts, along with many smaller products."," Below are real examples grouped by industry. Every one is taken from the official ",[30,1573,1576],{"href":1574,"rel":1575},"https://flutter.dev/showcase",[52],"Flutter showcase",", and each name links to the company's own case study there, so you can read what they built and why.",[14,1579,1580],{},"We are a Flutter agency, so we have an obvious interest here. That's why this list sticks to examples you can check yourself instead of our own claims.",[41,1582,1584],{"id":1583},"finance-and-payments","Finance and payments",[167,1586,1587,1597,1607,1617,1627],{},[170,1588,1589,1596],{},[17,1590,1591,333],{},[30,1592,1595],{"href":1593,"rel":1594},"https://flutter.dev/showcase/google-pay",[52],"Google Pay"," Google's payments app, which the showcase describes as \"going global\" with Flutter.",[170,1598,1599,1606],{},[17,1600,1601,333],{},[30,1602,1605],{"href":1603,"rel":1604},"https://flutter.dev/showcase/nubank",[52],"Nubank"," the Brazilian digital bank, scaling its mobile app with Flutter.",[170,1608,1609,1616],{},[17,1610,1611,333],{},[30,1612,1615],{"href":1613,"rel":1614},"https://flutter.dev/showcase/sofi",[52],"SoFi"," a finance \"superapp\" experience built with Flutter.",[170,1618,1619,1626],{},[17,1620,1621,333],{},[30,1622,1625],{"href":1623,"rel":1624},"https://flutter.dev/showcase/betterment",[52],"Betterment"," the investing app, scaling its development team on Flutter.",[170,1628,1629,197,1636,1643],{},[17,1630,1631],{},[30,1632,1635],{"href":1633,"rel":1634},"https://flutter.dev/showcase/virgin-money",[52],"Virgin Money",[17,1637,1638,333],{},[30,1639,1642],{"href":1640,"rel":1641},"https://flutter.dev/showcase/credit-agricole",[52],"Crédit Agricole"," banks that unified their app development on Flutter.",[14,1645,1646],{},"Finance apps are a good stress test: they need security reviews, accessibility and the same behavior on iOS and Android. That is the kind of work a single shared codebase makes easier to keep consistent.",[41,1648,1650],{"id":1649},"automotive-and-devices","Automotive and devices",[167,1652,1653,1663,1673,1683,1693],{},[170,1654,1655,1662],{},[17,1656,1657,333],{},[30,1658,1661],{"href":1659,"rel":1660},"https://flutter.dev/showcase/bmw",[52],"BMW"," which the showcase describes as scaling customer-centric product development at BMW Group with Flutter.",[170,1664,1665,1672],{},[17,1666,1667,333],{},[30,1668,1671],{"href":1669,"rel":1670},"https://flutter.dev/showcase/toyota",[52],"Toyota"," infotainment systems in Toyota vehicles.",[170,1674,1675,1682],{},[17,1676,1677,333],{},[30,1678,1681],{"href":1679,"rel":1680},"https://flutter.dev/showcase/lucid-motors",[52],"Lucid Motors"," a small team shipping a multiplatform app from one codebase.",[170,1684,1685,1692],{},[17,1686,1687,333],{},[30,1688,1691],{"href":1689,"rel":1690},"https://flutter.dev/showcase/lg-electronics",[52],"LG Electronics"," building parts of its next-generation webOS.",[170,1694,1695,1702],{},[17,1696,1697,333],{},[30,1698,1701],{"href":1699,"rel":1700},"https://flutter.dev/showcase/whirlpool",[52],"Whirlpool"," connected-appliance apps.",[14,1704,1705],{},"These show Flutter outside phones too: car screens, TVs and embedded devices.",[41,1707,1709],{"id":1708},"retail-food-and-marketplaces","Retail, food and marketplaces",[167,1711,1712,1722,1732,1749,1759],{},[170,1713,1714,1721],{},[17,1715,1716,333],{},[30,1717,1720],{"href":1718,"rel":1719},"https://flutter.dev/showcase/ebay",[52],"eBay"," eBay engineering teams building with Flutter.",[170,1723,1724,1731],{},[17,1725,1726,333],{},[30,1727,1730],{"href":1728,"rel":1729},"https://flutter.dev/showcase/alibaba-group",[52],"Alibaba Group"," scaling China's largest second-hand marketplace with Flutter.",[170,1733,1734,197,1741,1748],{},[17,1735,1736],{},[30,1737,1740],{"href":1738,"rel":1739},"https://flutter.dev/showcase/wolt",[52],"Wolt",[17,1742,1743,333],{},[30,1744,1747],{"href":1745,"rel":1746},"https://flutter.dev/showcase/talabat",[52],"talabat"," food and grocery delivery apps.",[170,1750,1751,1758],{},[17,1752,1753,333],{},[30,1754,1757],{"href":1755,"rel":1756},"https://flutter.dev/showcase/caribou-coffee",[52],"Caribou Coffee"," migrated its ordering app to a single Flutter codebase.",[170,1760,1761,1768],{},[17,1762,1763,333],{},[30,1764,1767],{"href":1765,"rel":1766},"https://flutter.dev/showcase/kijiji1",[52],"Kijiji"," the Canadian classifieds marketplace.",[41,1770,1772],{"id":1771},"health-fitness-and-wellbeing","Health, fitness and wellbeing",[167,1774,1775,1785,1795,1805],{},[170,1776,1777,1784],{},[17,1778,1779,333],{},[30,1780,1783],{"href":1781,"rel":1782},"https://flutter.dev/showcase/headspace",[52],"Headspace"," the meditation and mental health app.",[170,1786,1787,1794],{},[17,1788,1789,333],{},[30,1790,1793],{"href":1791,"rel":1792},"https://flutter.dev/showcase/tonal",[52],"Tonal"," the connected strength-training system, using Flutter for feature parity across platforms.",[170,1796,1797,1804],{},[17,1798,1799,333],{},[30,1800,1803],{"href":1801,"rel":1802},"https://flutter.dev/showcase/cz-zorgverzekeringen",[52],"CZ Zorgverzekeringen"," a Dutch health insurer's member app.",[170,1806,1807,1814],{},[17,1808,1809,333],{},[30,1810,1813],{"href":1811,"rel":1812},"https://flutter.dev/showcase/us-department-of-veterans-affairs",[52],"US Department of Veterans Affairs"," STAIR, an app that helps veterans adjust to civilian life.",[41,1816,1818],{"id":1817},"travel-entertainment-and-hospitality","Travel, entertainment and hospitality",[167,1820,1821,1831,1841,1851,1861],{},[170,1822,1823,1830],{},[17,1824,1825,333],{},[30,1826,1829],{"href":1827,"rel":1828},"https://flutter.dev/showcase/mgm-resorts",[52],"MGM Resorts"," the guest app for MGM's resorts.",[170,1832,1833,1840],{},[17,1834,1835,333],{},[30,1836,1839],{"href":1837,"rel":1838},"https://flutter.dev/showcase/universal-studios",[52],"Universal Studios"," theme park guest experiences.",[170,1842,1843,1850],{},[17,1844,1845,333],{},[30,1846,1849],{"href":1847,"rel":1848},"https://flutter.dev/showcase/sncf-connect",[52],"SNCF Connect"," France's rail booking app.",[170,1852,1853,1860],{},[17,1854,1855,333],{},[30,1856,1859],{"href":1857,"rel":1858},"https://flutter.dev/showcase/hamilton",[52],"Hamilton"," the official app for the musical.",[170,1862,1863,1870],{},[17,1864,1865,333],{},[30,1866,1869],{"href":1867,"rel":1868},"https://flutter.dev/showcase/pubg-mobile",[52],"PUBG MOBILE"," which the showcase describes as connecting players with Flutter.",[41,1872,1874],{"id":1873},"productivity-education-and-software","Productivity, education and software",[167,1876,1877,1887,1897,1907,1924],{},[170,1878,1879,1886],{},[17,1880,1881,333],{},[30,1882,1885],{"href":1883,"rel":1884},"https://flutter.dev/showcase/notebooklm",[52],"NotebookLM"," Google's AI research and note-taking app.",[170,1888,1889,1896],{},[17,1890,1891,333],{},[30,1892,1895],{"href":1893,"rel":1894},"https://flutter.dev/showcase/google-classroom",[52],"Google Classroom"," listed in the showcase among Google's own Flutter apps.",[170,1898,1899,1906],{},[17,1900,1901,333],{},[30,1902,1905],{"href":1903,"rel":1904},"https://flutter.dev/showcase/zoho-tables",[52],"Zoho Tables"," a spreadsheet-database hybrid.",[170,1908,1909,197,1916,1923],{},[17,1910,1911],{},[30,1912,1915],{"href":1913,"rel":1914},"https://flutter.dev/showcase/superlist",[52],"Superlist",[17,1917,1918,333],{},[30,1919,1922],{"href":1920,"rel":1921},"https://flutter.dev/showcase/rows",[52],"Rows"," a task manager and a spreadsheet tool built for desktop, web and mobile.",[170,1925,1926,1933],{},[17,1927,1928,333],{},[30,1929,1932],{"href":1930,"rel":1931},"https://flutter.dev/showcase/rive",[52],"Rive"," an animation tool rewritten entirely in Flutter.",[41,1935,1937],{"id":1936},"our-own-example-workspace369","Our own example: Workspace369",[14,1939,1940,1941,1943],{},"App369's own product, ",[30,1942,1488],{"href":1487},", is a Flutter app for freelancers and small service businesses: CRM, proposals, invoicing, scheduling and a shared inbox. One Flutter codebase serves both the iOS app and the web app. The case study covers the architecture and what we learned building it.",[41,1945,1947],{"id":1946},"what-these-examples-tell-you","What these examples tell you",[167,1949,1950,1956,1962,1968],{},[170,1951,1952,1955],{},[17,1953,1954],{},"Flutter is not just for prototypes."," Banks, carmakers and large consumer apps run it in production.",[170,1957,1958,1961],{},[17,1959,1960],{},"The common reason is one codebase."," Most of these case studies come back to the same point: shipping the same features to iOS, Android and often the web without maintaining separate apps.",[170,1963,1964,1967],{},[17,1965,1966],{},"It reaches beyond phones."," Car infotainment, TVs, desktop tools and the web all appear above.",[170,1969,1970,1973,1974,1978],{},[17,1971,1972],{},"It is not the right tool for everything."," Apps built around platform extensions like widgets, watch apps or brand-new OS features can still be simpler to build natively. See ",[30,1975,1977],{"href":1976},"/blog/flutter-vs-native-app-development-2026/","Flutter vs native"," for how to decide.",[41,1980,1982],{"id":1981},"planning-a-flutter-app","Planning a Flutter app?",[14,1984,1985,1986,1990,1991,1993,1994,39],{},"App369 builds Flutter apps for startups and small businesses from Boynton Beach, Florida. See ",[30,1987,1989],{"href":1988},"/service/flutter-development/","Flutter app development",", get a ballpark with the ",[30,1992,1529],{"href":1528},", or compare ",[30,1995,1997],{"href":1996},"/compare/flutter-vs-react-native/","Flutter vs React Native",{"title":61,"searchDepth":82,"depth":82,"links":1999},[2000,2001,2002,2003,2004,2005,2006,2007,2008],{"id":1583,"depth":82,"text":1584},{"id":1649,"depth":82,"text":1650},{"id":1708,"depth":82,"text":1709},{"id":1771,"depth":82,"text":1772},{"id":1817,"depth":82,"text":1818},{"id":1873,"depth":82,"text":1874},{"id":1936,"depth":82,"text":1937},{"id":1946,"depth":82,"text":1947},{"id":1981,"depth":82,"text":1982},"Real apps built with Flutter in 2026, grouped by industry: Google Pay, BMW, Toyota, Nubank, Headspace, eBay, MGM Resorts and more, each linked to its case study on flutter.dev.",{"readingTimeStats":2011},{"text":2012,"minutes":2013,"time":2014,"words":2015},"3 min read",2.805,168300,561,"/blog/apps-built-with-flutter-examples-2026",{"title":1561,"description":2009},"blog/apps-built-with-flutter-examples-2026","apps built with flutter, flutter app examples, flutter apps, companies using flutter, flutter showcase, flutter app development","https://app369.com/blog/apps-built-with-flutter-examples-2026/","AU1--kJ_S65saMe5bArn7py-kDABmlXLw23e5Ap4Am4",[2023,2025,2028,2031,2034,2037],{"keyword":5,"url":1228,"title":5,"relevance":2024},59,{"keyword":2026,"url":2027,"title":2026,"relevance":425},"Firebase Security Rules: The Complete Guide for App Developers (2026)","https://app369.com/blog/firebase-security-guide-2026/",{"keyword":2029,"url":2030,"title":2029,"relevance":302},"How to Debug a Flutter and Firebase Bug: Find the Root Cause (2026)","https://app369.com/blog/how-to-debug-a-flutter-firebase-bug-2026/",{"keyword":2032,"url":2033,"title":2032,"relevance":289},"Firebase Cloud Functions Min Instances: What They Cost (2026)","https://app369.com/blog/firebase-cloud-functions-min-instances-cost-2026/",{"keyword":2035,"url":2036,"title":2035,"relevance":289},"How to Cut Your Firebase Bill: A Line-by-Line Cost Audit (2026)","https://app369.com/blog/how-to-cut-your-firebase-bill-2026/",{"keyword":2038,"url":2039,"title":2038,"relevance":283},"How to Write a 60-Second SaaS Explainer Video Script (Template, 2026)","https://app369.com/blog/saas-explainer-video-script-template-2026/",1791849336360]