[{"data":1,"prerenderedAt":284},["ShallowReactive",2],{"blog-is-safe/flutterflow":3},{"id":4,"title":5,"body":6,"category":257,"date":258,"dateModified":258,"description":259,"draft":260,"extension":261,"faq":262,"featured":260,"headerVariant":268,"image":269,"keywords":270,"meta":271,"navigation":272,"ogDescription":273,"ogTitle":269,"path":274,"readTime":275,"schemaOrg":276,"schemaType":277,"seo":278,"sitemap":279,"stem":280,"tags":281,"twitterCard":282,"__hash__":283},"blog/blog/is-safe/flutterflow.md","Is FlutterFlow Safe? No-Code Mobile App Security Review (2026)",{"type":7,"value":8,"toc":248},"minimark",[9,13,19,26,31,34,42,46,49,64,71,81,84,93,96,100,103,110,116,122,126,137,142,145,149,156,159,163,187,221,240],[10,11,12],"p",{},"FlutterFlow generates a real Flutter app, not a preview inside someone else's web wrapper. That's the appeal: you get an actual APK and IPA you can submit to the App Store and Play Store. It also means the usual \"just check DevTools\" security advice for web-based builders doesn't apply here. A compiled mobile app hides secrets differently, and founders moving from Bolt or Lovable to FlutterFlow often assume compiled means safe. It doesn't.",[14,15,16],"tldr",{},[10,17,18],{},"FlutterFlow is safe to build a production mobile app with. The generated code is real Flutter, and the default backend (Firebase) is mature infrastructure. The risks are configuration-level and mobile-specific: Firestore rules left in test mode, API keys pasted directly into Custom Code actions, and App State variables marked \"Persisted\" that land in plaintext local storage. None of these are FlutterFlow bugs. All of them are common in FlutterFlow apps we've scanned.",[20,21,23],"verdict-badge",{"verdict":22},"caution",[10,24,25],{},"Use with caution: solid platform, common misconfigurations",[27,28,30],"h2",{"id":29},"what-flutterflow-controls","What FlutterFlow Controls",[10,32,33],{},"FlutterFlow compiles your visual project into native Flutter/Dart source, which you can export or let FlutterFlow build and deploy for you. The build pipeline, the generated widget tree, and the FlutterFlow editor itself run on Google Cloud infrastructure with standard TLS and access controls. You're not managing servers or writing your own build scripts.",[10,35,36,37,41],{},"Where FlutterFlow connects to a backend, that backend is almost always ",[38,39,40],"strong",{},"Firebase",": Firestore or Realtime Database for data, Firebase Authentication for logins, Cloud Storage for files. FlutterFlow supports Supabase and generic REST APIs too, but the built-in wizards and most published templates assume Firebase. That matters, because it means Firebase's entire security model (and its entire list of common misconfigurations) transfers directly onto your FlutterFlow app.",[27,43,45],{"id":44},"firestore-rules-the-same-problem-different-builder","Firestore Rules: The Same Problem, Different Builder",[10,47,48],{},"Every new Firebase project offers a \"start in test mode\" option for Firestore. Test mode sets a rule that allows any read or write for 30 days with zero authentication check:",[50,51,53],"code-block",{"label":52},"Firestore test-mode default",[54,55,60],"pre",{"className":56,"code":58,"language":59},[57],"language-text","rules_version = '2';\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /{document=**} {\n      allow read, write: if request.time \u003C timestamp.date(2026, 8, 1);\n    }\n  }\n}\n","text",[61,62,58],"code",{"__ignoreMap":63},"",[10,65,66,67,70],{},"That 30-day window exists to let you prototype without fighting the rules language on day one. The problem is what happens after it expires, or worse, before it does: founders publish their FlutterFlow app while still in test mode, or replace the expiration rule with ",[61,68,69],{},"allow read, write: if true;"," because a permission error was blocking a demo and they needed it gone fast.",[72,73,74],"danger-box",{},[10,75,76,77,80],{},"If your Firestore rules still say ",[61,78,79],{},"if true"," or reference a date that's already passed, your entire database, every user's data, every document, is readable and writable by anyone who has your Firebase project's public API key. That key ships inside your compiled app, so it is not a secret. It's meant to identify the project, not authorize access. Rules are what authorize access.",[10,82,83],{},"A working rule scoped to the signed-in user looks like this instead:",[50,85,87],{"label":86},"Scoped Firestore rule",[54,88,91],{"className":89,"code":90,"language":59},[57],"match /users/{userId}/{document=**} {\n  allow read, write: if request.auth != null && request.auth.uid == userId;\n}\n",[61,92,90],{"__ignoreMap":63},[10,94,95],{},"Check this from the Firebase console (Firestore Database > Rules), not from inside the FlutterFlow editor. FlutterFlow shows you the collections; it does not audit whether your rules actually restrict access to them.",[27,97,99],{"id":98},"custom-code-and-the-compiled-binary-illusion","Custom Code and the Compiled-Binary Illusion",[10,101,102],{},"FlutterFlow's Custom Actions and Custom Functions let you drop in raw Dart code for anything the visual editor can't do: a third-party API call, a calculation, an integration FlutterFlow doesn't have a built-in component for. This is also where hardcoded API keys tend to end up, because pasting a key directly into a Custom Action is faster than wiring up FlutterFlow's App State or environment config.",[10,104,105,106,109],{},"With a web app, a hardcoded key is trivially visible: right-click, View Source, done. FlutterFlow apps compile to Flutter's AOT (ahead-of-time) format, a native ",[61,107,108],{},"libapp.so"," binary bundled inside the APK. There's no page source to view. That's a real difference, and it's why some founders assume a compiled app \"protects\" whatever they hardcoded.",[10,111,112,113,115],{},"It doesn't protect it, it just raises the bar. String constants, including a Dart string literal holding an API key, get written into the AOT snapshot's data section largely as-is. Anyone who unpacks the APK and runs a string-extraction pass against ",[61,114,108],{}," can pull them back out. This is a known category of Flutter reverse engineering, not a theoretical attack. The key isn't sitting in a network request header for a browser DevTools tab to catch, but it isn't gone either.",[117,118,119],"warning-box",{},[10,120,121],{},"Treat \"the user would need to decompile my app\" the same way you'd treat \"the user would need to view my page source\": as a speed bump, not a wall. Any API key that grants write access, spend, or account-level permissions belongs behind a server-side proxy (a Firebase Cloud Function or your own backend), never inside Custom Code.",[27,123,125],{"id":124},"app-state-persisted-doesnt-mean-encrypted","App State: \"Persisted\" Doesn't Mean Encrypted",[10,127,128,129,132,133,136],{},"FlutterFlow's App State lets you mark a variable as ",[38,130,131],{},"Persisted",", which keeps its value across app restarts. Under the hood, this uses Flutter's ",[61,134,135],{},"shared_preferences"," plugin: an XML file in app-private storage on Android, a plist on iOS.",[10,138,139,141],{},[61,140,135],{}," is intentionally simple, and it stores values in plaintext. That's fine for a theme toggle or a \"remember this filter\" setting. It's a real problem if you're persisting an auth token, a session ID, or anything else that would let someone impersonate a user, because on a rooted Android device or a jailbroken iPhone, that file is directly readable by other apps or a file manager.",[10,143,144],{},"If you need to persist something sensitive, use FlutterFlow's secure storage option where available, or route session handling through Firebase Auth's own token refresh (which already handles this correctly) rather than storing a raw token in App State yourself.",[27,146,148],{"id":147},"authentication-defaults","Authentication Defaults",[10,150,151,152,155],{},"FlutterFlow wires up Firebase Authentication through its own setup wizard, and it's easy to enable more sign-in methods than you're actually using. Anonymous auth is a common one left toggled on from an early prototyping step; it lets anyone create a session with zero credentials, which is fine for a demo and a problem if your Firestore rules only check ",[61,153,154],{},"request.auth != null"," (an anonymous session satisfies that check just as well as a real account does).",[10,157,158],{},"Audit the Sign-in Methods tab in the Firebase console, not just the FlutterFlow Auth settings screen, and disable anything you enabled to test but never actually shipped.",[27,160,162],{"id":161},"security-checklist-for-flutterflow-apps","Security Checklist for FlutterFlow Apps",[164,165,166,171,175,179,183],"checklist-section",{},[167,168],"checklist-item",{"description":169,"label":170},"No allow read, write: if true and no expired test-mode rule. Check the Firebase console directly, not the FlutterFlow editor.","Firestore rules are scoped, not open",[167,172],{"description":173,"label":174},"Anything with write access, spend limits, or account permissions goes through a Cloud Function proxy, not a Dart string literal.","No API keys hardcoded in Custom Code",[167,176],{"description":177,"label":178},"Auth tokens and session identifiers rely on Firebase Auth's own handling, not a plaintext shared_preferences entry.","Persisted App State avoids sensitive data",[167,180],{"description":181,"label":182},"Anonymous auth and any other sign-in method enabled for testing is turned off in the Firebase console before launch.","Unused auth methods disabled",[167,184],{"description":185,"label":186},"Cloud Storage has its own separate rules file. A locked-down Firestore with open Storage rules still leaks uploaded files.","Storage rules match Firestore rules",[188,189,190,197,203,209,215],"faq-section",{},[191,192,194],"faq-item",{"question":193},"Is FlutterFlow safe for a production app?",[10,195,196],{},"FlutterFlow itself is safe. It generates real Flutter/Dart code and typically wires up Firebase (Firestore, Auth, Storage) as the backend, both mature and well-audited. The risk is the same as every visual builder: Firestore rules left open, API keys pasted into Custom Code, and local storage treated as if it were encrypted when it isn't.",[191,198,200],{"question":199},"Can someone see my API keys in a FlutterFlow app?",[10,201,202],{},"Not the way they can in a web app's DevTools, but yes, eventually. FlutterFlow compiles to a real Flutter binary, so there's no view-source. But string constants in Custom Code (including hardcoded keys) get compiled into the AOT snapshot and are still recoverable with basic APK reverse-engineering tools. Obscured is not the same as protected.",[191,204,206],{"question":205},"Does FlutterFlow use Firebase by default?",[10,207,208],{},"Yes. FlutterFlow's built-in backend integration is Firebase (Firestore or Realtime Database, Firebase Auth, Cloud Storage). You can point it at Supabase or a custom REST API instead, but most FlutterFlow tutorials and templates default to Firebase, which means Firebase's security model, and its misconfiguration pitfalls, apply directly.",[191,210,212],{"question":211},"Are FlutterFlow Firestore rules secure by default?",[10,213,214],{},"No. Firebase projects start new Firestore databases in test mode, which allows any read or write for 30 days with no authentication check. FlutterFlow doesn't override this. If you publish before writing real rules, or the test-mode window lapses and nobody noticed, your entire database is open to anyone who finds your project's API key.",[191,216,218],{"question":217},"Is data stored in FlutterFlow App State secure?",[10,219,220],{},"Persisted App State variables are written to the device using Flutter's shared_preferences plugin, which stores values in plaintext (an XML file on Android, a plist on iOS). Fine for a theme preference or a cached username. Not fine for an auth token, a payment credential, or anything you wouldn't want visible to another app with storage access on a rooted or jailbroken device.",[222,223,224,230,235],"related-articles",{},[225,226],"related-card",{"description":227,"href":228,"title":229},"The backend FlutterFlow uses by default: security rules, Authentication, and the misconfigurations that show up across every Firebase-backed app.","/blog/is-safe/firebase","Is Firebase Safe?",[225,231],{"description":232,"href":233,"title":234},"A pre-launch checklist for Firestore rules, Storage rules, App Check, and Authentication settings.","/blog/checklists/firebase-security-checklist","Firebase Security Checklist",[225,236],{"description":237,"href":238,"title":239},"Another visual builder, different risk profile: CMS API token scope, Memberships limitations, and unsandboxed custom code.","/blog/is-safe/webflow","Is Webflow Safe?",[241,242,245],"cta-box",{"href":243,"label":244},"/","Scan Your FlutterFlow App",[10,246,247],{},"CheckYourVibe checks your app's live endpoints and configuration for open Firestore rules, exposed keys, and missing auth checks before you ship.",{"title":63,"searchDepth":249,"depth":249,"links":250},2,[251,252,253,254,255,256],{"id":29,"depth":249,"text":30},{"id":44,"depth":249,"text":45},{"id":98,"depth":249,"text":99},{"id":124,"depth":249,"text":125},{"id":147,"depth":249,"text":148},{"id":161,"depth":249,"text":162},"is-safe","2026-07-01","Is FlutterFlow safe for shipping a real mobile app? Firestore rules, hardcoded API keys in Custom Code, and why a compiled Flutter app hides secrets differently than a website.",false,"md",[263,264,265,266,267],{"question":193,"answer":196},{"question":199,"answer":202},{"question":205,"answer":208},{"question":211,"answer":214},{"question":217,"answer":220},"amber",null,"is flutterflow safe, flutterflow security, flutterflow firestore rules, flutterflow api key, flutterflow custom code security",{},true,"FlutterFlow security review: Firestore rule defaults, Custom Code API key exposure, and local storage risks in compiled Flutter apps.","/blog/is-safe/flutterflow","7 min read","[object Object]","BlogPosting",{"title":5,"description":259},{"loc":274},"blog/is-safe/flutterflow",[],"summary_large_image","Pzyxp0tCVrUyQNInWlmmJm9LHO8oHc_2o17M7jS_DxQ",1784736378961]