← Articles

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

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.

Sign in to like, dislike, or report.

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