dart-define vs .env: managing Flutter config the right way

By Arjun Menon · 24 January 2026879 views
dart-define vs .env: managing Flutter config the right way

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

ScenarioUse
API URL differs per environmentdart-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.

Comments

No comments yet. Be the first!

Sign in to leave a comment.