The .env file stores environment variables in a simple key-value format and separates configuration from the application source code. According to The Twelve-Factor App (2011), configuration must be strictly separated from code, and .env files have become the standard for this approach. .env File allows injecting different values of API keys, server URLs, and build flags without recompiling the project.
Key Takeaways
.env File is a configuration file that stores environment variables in a simple text format KEY=VALUE. Each line contains one variable: the key name and its value, separated by an equals sign.
.env files solve a fundamental problem of modern development: different environments (local, staging, production) require completely different settings. The API server URL on a local machine is http://localhost:8080, on a production server it is https://api.production.com. If these values are hardcoded directly into the application code, each build for a different environment requires changing the source code.
The practice of storing configuration outside the main application code was standardized in The Twelve-Factor App manifest (2011), which identified environment variables as the only correct way to configure an application. According to the JetBrains Developer Ecosystem survey (2024), more than 67% of mobile developers use .env files in their projects.
For mobile development, .env provides an additional advantage: values are substituted at the build stage via Gradle (Android) or xcconfig (iOS), allowing separate builds for development, staging, and production without changing the source code.
.env is especially useful when working in a team: each developer creates their own local .env with settings tailored to their environment (local DB path, debug API keys), while common settings are committed as .env.example in the repository. This eliminates the situation where after a git pull a developer's build breaks due to a missing environment variable they were unaware of. A new team member simply copies .env.example to .env and fills in their local values.
The .env format is as simple as possible: each line is one variable in the form KEY=VALUE. Spaces around the equals sign are usually ignored, but in most libraries they are considered part of the value, so it is better to avoid them.
Comments start with the # character — the entire line after it is ignored. Empty lines are also skipped. If the value contains spaces, it is enclosed in double or single quotes.
# Basic environment settings
APP_NAME=MyMobileApp
APP_ENV=development
# API configuration
API_BASE_URL=http://localhost:3000/api
API_TIMEOUT=30000
# Sensitive data
DB_PASSWORD=secret_password_123
JWT_SECRET=your_jwt_secret_key
All variables in .env are strings, but loader libraries may cast them to the required type. Backslashes and quotes are used for escaping special characters. If a value contains the # character as part of the text, it must be escaped as \#.
KEY=value or KEY="value with spaces"PORT=8080DEBUG=trueKEY=line1\
line2DB_URL=${DB_HOST}:${DB_PORT}When loading .env, libraries may perform variable interpolation — substituting the values of some keys inside others. For example, the variable DATABASE_URL=postgres://${DB_USER}:${DB_PASS}@localhost/db will expand DB_USER and DB_PASS from the same file.
The method of connecting .env depends on the platform. Android uses Gradle plugins, iOS uses xcconfig configuration files, and cross-platform solutions like Flutter use specialized libraries.
In Android, .env is loaded via the gradle-dotenv plugin. The plugin reads .env from the project root and adds the values to BuildConfig, after which they are available in Kotlin or Java code through generated fields.
// build.gradle.kts (app level)
plugins {
id("co.uzzu.dotenv") version "4.0.0"
}
android {
buildFeatures {
buildConfig = true
}
}
kotlin {
// Access in code: BuildConfig.API_BASE_URL
buildConfigField("String", "API_BASE_URL",
"\"" + dotenv.get("API_BASE_URL") + "\"")
}
In iOS, environment variables are usually configured through xcconfig files. To load .env in Swift, the DotEnv library or the built-in Info.plist mechanism with custom keys is used.
// Loading .env in Swift project
import DotEnv
struct AppConfig {
static func load() {
let env = DotEnv(Bundle.main)
env.load()
let apiURL = ProcessInfo.processInfo
.environment["API_BASE_URL"] ??
"https://default.api.com"
}
}
For Flutter, there is the flutter_dotenv package, which loads variables from .env during application initialization. The .env file is placed in the project root, and the variables become available through the dotenv class.
// pubspec.yaml
dependencies:
flutter_dotenv: ^5.1
// main.dart — loading at startup
import 'package:flutter_dotenv/flutter_dotenv.dart';
void main() async {
await dotenv.load(fileName: '.env');
var apiUrl = dotenv.get('API_BASE_URL');
runApp(MyApp(baseUrl: apiUrl));
}
All three approaches share a common principle: .env is loaded at build time or when the application starts, the values are cached and used in code through generated constants. This prevents sensitive data from ending up in the repository.
For React Native, the react-native-config package is used, which at build time automatically generates a BuildConfig class for Android and constants in Info.plist for iOS from a single .env file in the project root. This is especially convenient for startups using Expo or bare workflow: a single .env at the root level is enough for all platforms to receive the same environment variables without duplicating configurations.
Despite all the advantages, .env is not a full-fledged solution for storing secrets in a production environment. It provides a basic level of protection, but if used incorrectly, it can lead to leakage of confidential data.
The most important rule — .env must never end up in the version control system of the repository. The file is added to .gitignore immediately after creation, and only the sample file .env.example with empty or dummy values is committed to the repository.
# .env.example — committed to repository
APP_NAME=
APP_ENV=development
API_BASE_URL=http://localhost:3000
API_TIMEOUT=30000
# DB_PASSWORD — do not specify even in the example!
# JWT_SECRET — do not specify even in the example!
# .gitignore
# Dotenv files
.env
.env*.local
For production projects, it is recommended to use professional secret management solutions. .env in production is only acceptable if the file is located outside the server document-root and has strict access permissions.
According to the Snyk State of Open Source Security (2024), .env file leaks through repositories caused more than 12% of all API key disclosure incidents among surveyed companies. Using a dedicated secret manager reduces this risk to zero.
Additional protection is achieved by implementing pre-commit hooks using tools like husky and lint-staged, which check whether a developer accidentally added .env to a commit. Tools like git-secrets (AWS) and talisman scan each commit for patterns of API keys, tokens, and passwords, blocking the commit if detected. For CI pipelines, it is recommended to add detect-secrets — an automatic scanner that will not let a .env file into the repository even if a developer makes a mistake.
Frequently Asked Questions
No, .env should not be committed to Git. The file contains sensitive data and should be added to .gitignore. Instead, .env.example with a template of all required variables is placed in the repository.
.env is the real file with production values that is never committed. The .env.example file contains the same keys but with empty or fake values — it is committed to the repository as a template for new developers.
Yes, but it is not recommended without additional protection. If .env is used on a production server, the file must be located outside the document-root of the web server with access permissions set to 600 (owner only). For critical projects, secret managers are preferred.
Through the gradle-dotenv plugin (co.uzzu.dotenv). The plugin reads .env from the project root and exports the values to BuildConfig. Variables become available in code as BuildConfig.VARIABLE_NAME at compile time.
Yes, many parsers support interpolation in the format ${VAR_NAME}. For example, URL=${HOST}:${PORT} will substitute the values of HOST and PORT from the same file. However, this capability depends on the specific loader library.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also