dart-define vs .env: managing Flutter config the right way
By Ann Tech · 24 January 2026
Dart projects have two common ways to pass configuration values into a Flutter build: --dart-define and .env files. They look similar but work differently. Here is when to use each and how to combine them correctly.
dart-define: compile-time constants
--dart-define embeds values into the binary at build time:
flutter run \
--dart-define=API_BASE_URL=https://api.staging.example.com \
--dart-define=STRIPE_KEY=pk_test_xxx \
--dart-define=FEATURE_CHAT=true
// Read with const — resolved at compile time, can be used in const constructors
const apiUrl = String.fromEnvironment('API_BASE_URL', defaultValue: 'https://api.example.com');
const stripeKey = String.fromEnvironment('STRIPE_KEY');
const isChatEnabled = bool.fromEnvironment('FEATURE_CHAT');
Because they're const, they can be used anywhere a compile-time constant is required:
const Dio(BaseOptions(baseUrl: apiUrl)) // Works only because apiUrl is const
.env files: developer convenience
.env files are a local developer convention — they're NOT loaded by Flutter automatically. You need a package or script to read them.
# .env.staging
API_BASE_URL=https://api.staging.example.com
STRIPE_KEY=pk_test_xxx
FEATURE_CHAT=true
Option 1: flutter_dotenv
dependencies:
flutter_dotenv: ^5.1.0
flutter:
assets:
- .env # Bundled as an asset — readable at runtime
await dotenv.load(fileName: '.env');
final apiUrl = dotenv.env['API_BASE_URL'] ?? 'https://api.example.com';
Warning: this bundles .env contents into your app binary. Do NOT put secrets here.
Option 2: shell script to convert .env to dart-define
#!/bin/bash
# scripts/run_with_env.sh
ENV_FILE=".env.${1:-development}"
if [ ! -f "$ENV_FILE" ]; then
echo "No $ENV_FILE found"; exit 1
fi
# Convert .env to --dart-define flags
DART_DEFINES=$(grep -v '^#' $ENV_FILE | grep '=' | sed 's/^/--dart-define=/' | tr '\n' ' ')
flutter run $DART_DEFINES ${@:2}
Usage:
bash scripts/run_with_env.sh staging
# Equivalent to: flutter run --dart-define=API_BASE_URL=... --dart-define=...
dart-define-from-file (Flutter 3.7+)
Flutter added built-in support for JSON config files:
// config/staging.json
{
"API_BASE_URL": "https://api.staging.example.com",
"STRIPE_KEY": "pk_test_xxx",
"FEATURE_CHAT": "true"
}
flutter run --dart-define-from-file=config/staging.json
flutter build apk --dart-define-from-file=config/production.json
Same String.fromEnvironment access on the Dart side. This is the cleanest approach for multi-environment setups.
Native side access
dart-define values are also accessible in native Android/iOS code:
Android (build.gradle.kts):
android {
buildTypes {
release {
buildConfigField(
"String",
"API_BASE_URL",
"\"${project.findProperty('API_BASE_URL') ?: ''}\""
)
}
}
}
iOS (in Xcode Build Settings or via a pre-build script):
# In a Run Script build phase:
echo "API_BASE_URL=${API_BASE_URL}" >> "${SRCROOT}/.env"
Environment strategy
config/
├── development.json # Local dev: local API, test keys
├── staging.json # Staging: staging API, test keys
└── production.json # Production: real API, live keys
# .gitignore
config/*.json # Never commit keys to git
# CI — secrets from GitHub Actions secrets
- name: Create config
run: |
echo '{
"API_BASE_URL": "${{ secrets.API_BASE_URL_PROD }}",
"STRIPE_KEY": "${{ secrets.STRIPE_KEY_PROD }}"
}' > config/production.json
- name: Build
run: flutter build apk --dart-define-from-file=config/production.json
When to use which
| Scenario | Use |
|---|---|
| API URL differs per environment | dart-define or dart-define-from-file |
| Secret keys (payment, auth) | dart-define (from CI secrets) |
| Feature flags (few, stable) | dart-define |
| Feature flags (dynamic, per-user) | Firebase Remote Config |
| Local dev convenience | .env.development + conversion script |
Common pitfalls
Bundling secrets in .env as an asset. flutter_dotenv bundles the file in your app — it's readable with a hex editor. Only use it for non-secret configuration. Use dart-define (via CI secrets) for keys.
No default values. String.fromEnvironment('API_URL') returns empty string if not set. Always provide a default: String.fromEnvironment('API_URL', defaultValue: 'https://api.example.com').
Forgetting that dart-define is baked in at compile time. You can't change dart-define values at runtime. For values that need to change after release (feature flags, config thresholds), use Firebase Remote Config instead.
Sign in to like, dislike, or report.