
If you're searching for how to hide an API key in Flutter, the honest answer is that you can't hide it inside the app. --dart-define, a .env file and Gradle properties all end up in the build you ship. What you can do is decide which keys are safe to ship, restrict those, and move the real secrets behind a server that only answers your genuine app. Here's how I do each of those.
Can you hide an API key in a Flutter app?
No. Every value your app reads at runtime has to be inside the app package, including values injected at build time. Anyone who downloads your release can extract them. Flutter's documentation says it directly: "It is a poor security practice to store secrets in an app." The flutter_dotenv README also says not to keep API keys, tokens or passwords in a .env file, because assets are bundled into the app.
| Where the value lives | How it gets into the app | Secret from users? |
|---|---|---|
| A constant in Dart code | Compiled into the app | No |
--dart-define or --dart-define-from-file | Compiled in at build time | No |
.env loaded with flutter_dotenv | Bundled as an asset | No |
local.properties, Gradle properties, .xcconfig | Written into BuildConfig or Info.plist | No |
flutter_secure_storage | Stored on the device after the app receives it | Protected on the device, but it still had to arrive from somewhere |
| Your backend's secret store | Never sent to the app | Yes |

Using dart-define and environment variables the right way
Build-time values are still useful. They keep config out of Git and let you switch settings per flavour. Use them for config, not secrets. A config/prod.json file:
{ "API_BASE_URL": "https://api.example.com", "MAPS_KEY": "AIza..." }flutter run --dart-define-from-file=config/prod.jsonconst apiBaseUrl = String.fromEnvironment('API_BASE_URL');
const mapsKey = String.fromEnvironment('MAPS_KEY');Does --obfuscate protect API keys in Flutter?
No. Two different tools get mixed up here:
- R8 on Android. Flutter enables R8 shrinking by default for release APKs and app bundles. It works on the Java and Kotlin side and doesn't touch your Dart code.
- Dart obfuscation. Only applied when you build with
--obfuscateand--split-debug-info. Flutter's docs say it "does not encrypt resources nor does it protect against reverse engineering. It only renames symbols with more obscure names."
flutter build appbundle --release \
--obfuscate --split-debug-info=build/symbols/androidKeep the symbols directory so you can read crash stack traces. Obfuscation makes your code harder to read. It does nothing for a key stored as a string.
Keys that are fine to ship (with restrictions)
- Firebase API keys. Firebase says these are OK to include in code. Your data is protected by Security Rules and App Check, not by the key. Apply the API restrictions Firebase recommends.
- Stripe publishable keys (
pk_...). Stripe lists them as safe to expose. They can't create charges or read account data. - Google Maps keys. These have to be in the app for the SDK to work, so restrict them in Google Cloud console. Google's guidance is to add an application restriction and at least one API restriction. For Android, that's the package name plus the SHA-1 of each signing certificate (check Play Console's App signing page if Google signs your releases). For iOS, it's the bundle ID.
What about a Gemini or OpenAI API key in Flutter?
This is the most common question I get now. Google's Gemini API docs say not to hardcode API keys in web or mobile apps, and to run a backend proxy for client-side apps. That applies to any provider that bills per request.
For Gemini specifically, Firebase AI Logic is the managed version of that proxy. The firebase_ai package (5.0.0 at the time of writing) calls Gemini through Firebase, so no Gemini key ships in the app, and you protect it with App Check:
import 'package:firebase_ai/firebase_ai.dart';
final model = FirebaseAI.googleAI().generativeModel(model: 'gemini-3.5-flash-lite');
Future<String?> summarise(String text) async {
final res = await model.generateContent([Content.text('Summarise: $text')]);
return res.text;
}Firebase's docs say the console has enforced App Check for Firebase AI Logic during guided setup since early July 2026. If you set it up before then, check enforcement yourself. For OpenAI, Anthropic or anything else, use your own proxy as shown next.
Put real secrets behind a backend proxy
The app calls your backend, the backend checks who's calling, and only the backend calls the third-party API with the real key.

With Cloud Functions for Firebase, the key lives in Cloud Secret Manager through defineSecret, and the function rejects calls without a valid App Check token:
import { onCall, HttpsError } from "firebase-functions/v2/https";
import { defineSecret } from "firebase-functions/params";
const providerKey = defineSecret("PROVIDER_API_KEY");
export const summarize = onCall(
{ secrets: [providerKey], enforceAppCheck: true },
async (req) => {
if (!req.auth) throw new HttpsError("unauthenticated", "Sign in first");
const text = String(req.data.text ?? "").slice(0, 4000);
const res = await fetch("https://api.example.com/v1/summarize", {
method: "POST",
headers: {
Authorization: `Bearer ${providerKey.value()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text }),
});
if (!res.ok) throw new HttpsError("unavailable", "Provider error");
return await res.json();
}
);Set the secret once with firebase functions:secrets:set PROVIDER_API_KEY. The proxy is also where per-user rate limits, input size limits and logging go. If the key leaks from your server, you rotate it in one place, with no app update.
Set up Firebase App Check in Flutter
A proxy alone still answers anyone who finds the URL. App Check adds an attestation that the request comes from your app on a real device: Play Integrity on Android, and App Attest (iOS 14 and later) or DeviceCheck on Apple platforms.
import 'package:firebase_app_check/firebase_app_check.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseAppCheck.instance.activate(
providerAndroid: kDebugMode
? const AndroidDebugProvider()
: const AndroidPlayIntegrityProvider(),
providerApple: kDebugMode
? const AppleDebugProvider()
: const AppleAppAttestWithDeviceCheckFallbackProvider(),
);
runApp(const App());
}If your backend isn't Cloud Functions, get a token with FirebaseAppCheck.instance.getToken(), send it in the X-Firebase-AppCheck header (Firebase warns against putting it in a URL) and verify it with the Admin SDK:
import { getAppCheck } from "firebase-admin/app-check";
app.addHook("preHandler", async (request, reply) => {
const token = request.headers["x-firebase-appcheck"];
if (typeof token !== "string") return reply.code(401).send();
try {
await getAppCheck().verifyToken(token);
} catch {
return reply.code(401).send();
}
});Firebase says App Check "prevents some, but not all, abuse vectors", and Google describes Play Integrity as one signal, not the only one. Keep authentication and rate limits too, and watch App Check metrics before enforcing so older app versions aren't locked out.
Where flutter_secure_storage fits
flutter_secure_storage keeps values in the iOS Keychain and in Android Keystore-backed encryption. Use it for what the app receives at runtime, such as a user's refresh token. It doesn't fix a shipped secret, because the key would have to reach the app first.
Checklist before release
- List every key in the project and sort it: public, public but restricted, or secret.
- Move every secret to a server and rotate it if it was ever in a build or a public commit.
- Restrict Maps and Firebase keys by app and by API.
- Turn on App Check, monitor, then enforce.
- Build with
--obfuscate --split-debug-infoand keep the symbols. - Run a secret scanner such as gitleaks on pull requests, and keep signing keys in your CI secret store.
The same "the server decides" rule applies to payments. My Flutter in-app purchase guide verifies purchases on the server, and the Flutter Stripe guide explains why the Stripe secret key never ships.
Frequently asked questions
How do I hide an API key in Flutter?
You can't hide it inside the app, because anything in the build can be extracted. Ship only keys designed to be public, restrict them, and keep secret keys on a server that calls the API for the app.
Does --dart-define keep my API key secret?
No. Values passed with --dart-define or --dart-define-from-file are compiled into the app. Use them for configuration, not secrets.
Is it safe to put a .env file in my Flutter app?
Only for non-sensitive settings. The flutter_dotenv README says not to store API keys, tokens or passwords in it, because assets are bundled into the app.
Does --obfuscate protect API keys?
No. Flutter's docs say obfuscation only renames symbols and doesn't encrypt resources or protect against reverse engineering.
How do I use a Gemini API key in a Flutter app safely?
Don't put it in the app. Google's Gemini docs say not to hardcode keys in mobile apps. Use Firebase AI Logic with the firebase_ai package and App Check, or call Gemini from your own backend.
How do I secure a Google Maps API key in Flutter?
Restrict it in Google Cloud console with an application restriction (Android package name and SHA-1, iOS bundle ID) and at least one API restriction.
Is my Firebase API key a secret?
No. Firebase says API keys for Firebase services are fine to include in code. Protect data with Security Rules and App Check.
Do I still need a backend if I use App Check?
Yes, for any third-party secret. App Check proves a request comes from your app, but the secret still has to live on a server.
Sources
- Obfuscate Dart code (Flutter)
- Build and release an Android app (Shrink your code with R8) (Flutter)
- flutter_dotenv README (pub.dev)
- Learn about using and managing API keys for Firebase (Firebase)
- API keys (Stripe)
- Firebase App Check (Firebase)
- Get started using App Check in Flutter apps (Firebase)
- firebase_app_check changelog (0.5.0) (pub.dev)
- Enable App Check enforcement for Cloud Functions (Firebase)
- Verify App Check tokens from a custom backend (Firebase)
- Protect custom backend resources with App Check in Flutter (Firebase)
- Configure your environment (secret parameters) (Firebase)
- Play Integrity API overview (Google)
- DCAppAttestService (Apple)
- flutter_secure_storage (pub.dev)
- Using Gemini API keys (Google)
- Implement Firebase App Check for Firebase AI Logic (Firebase)
- firebase_ai (pub.dev)
- Google Maps Platform security guidance (Google)
Keep reading
- Flutter In-App Purchase Guide: StoreKit 2, Play Billing and RevenueCatA Flutter in-app purchase guide for 2026: a subscription example, server verification on Firebase, testing, cancelling, and when RevenueCat is the better call.
- Flutter Stripe Integration: PaymentSheet, Webhooks and ConnectIntegrate Stripe in Flutter with flutter_stripe: PaymentSheet, Apple Pay and Google Pay, webhooks that fulfil orders, and Stripe Connect for marketplaces.
- Taking Over an Existing Flutter App and PHP Backend: My PlaybookHow I take over a live Flutter app with a legacy PHP backend: a first-week checklist, reproducing bugs, PHP security fixes and a safe move to token auth.
Comments
Share a question or note about this article.
Loading comments…