Dotenv file parser with typed getters and multi-environment support
dart pub add philiprehberger_env
Dotenv file parser with typed getters and multi-environment support
Add to your pubspec.yaml:
dependencies:
philiprehberger_env: ^0.6.0
Then run:
dart pub get
import 'package:philiprehberger_env/philiprehberger_env.dart';
final env = Env.fromString('PORT=8080\nDEBUG=true');
print(env.getInt('PORT')); // 8080
print(env.getBool('DEBUG')); // true
final content = '''
# Database config
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DEBUG=true
TAGS=web,api,auth
''';
final env = Env.fromString(content);
print(env.getString('DB_HOST')); // localhost
final env = Env.fromString('PORT=8080\nRATE=3.14\nDEBUG=true\nTAGS=a,b,c\nURL=https://example.com\nLOG_LEVEL=info');
env.getString('PORT'); // "8080"
env.getInt('PORT'); // 8080
env.getDouble('RATE'); // 3.14
env.getBool('DEBUG'); // true
env.getList('TAGS'); // ["a", "b", "c"]
env.getUri('URL'); // Uri(https://example.com)
env.getEnum('LOG_LEVEL', LogLevel.values); // LogLevel.info
env.getString('MISSING', defaultValue: 'n/a'); // "n/a"
env.has('PORT'); // true
env.keys; // ('PORT', 'DEBUG', ...)
final env = Env.fromString('HOST=localhost\nURL=http://\${HOST}:8080');
print(env.getString('URL')); // http://localhost:8080
Use ${VAR:-default} syntax to provide a fallback value when a variable is missing or empty:
final env = Env.fromString('URL=\${API_HOST:-localhost}:\${API_PORT:-3000}');
print(env.getString('URL')); // localhost:3000
Combine multiple Env instances with override semantics:
final defaults = Env({'PORT': '3000', 'HOST': 'localhost'});
final overrides = Env({'PORT': '8080'});
final env = defaults.merge(overrides);
print(env.getInt('PORT')); // 8080 (overridden)
print(env.getString('HOST')); // localhost (from defaults)
import 'package:philiprehberger_env/philiprehberger_env.dart';
final env = Env.fromPlatform();
print(env.getString('HOME'));
print(env.getString('PATH'));
final env = Env.fromFile('.env');
print(env.getString('DB_HOST'));
Layer environment files in priority order — later paths win. The typical pattern is .env for shared defaults and .env.local for machine-specific overrides:
final env = Env.fromFiles(['.env', '.env.local']);
print(env.getString('DB_HOST')); // .env.local wins if set
Throws FileSystemException if any path is missing — wrap individual paths in try/catch if a file is optional.
final env = Env.fromString('DEPLOYED_AT=2026-05-13T22:30:00Z\nTIMEOUT=30s\nRETRY=500ms\nTTL=2h');
env.getDateTime('DEPLOYED_AT'); // DateTime(2026-05-13 22:30:00Z)
env.getDuration('TIMEOUT'); // Duration(seconds: 30)
env.getDuration('RETRY'); // Duration(milliseconds: 500)
env.getDuration('TTL'); // Duration(hours: 2)
Duration suffixes: ms, s, m, h, d. A bare integer is treated as milliseconds.
Extract a subset of an Env by prefix (e.g. all database settings) or by an arbitrary predicate:
final env = Env.fromString('DB_HOST=localhost\nDB_PORT=5432\nAPP_NAME=demo');
final db = env.prefixed('DB_', stripPrefix: true);
print(db.getString('HOST')); // localhost
print(db.getInt('PORT')); // 5432
final secrets = env.filter((key, value) => key.endsWith('_TOKEN'));
Both methods return new Env instances — the original is unchanged.
final env = Env.fromString('BALANCE=99999999999999999999');
env.getBigInt('BALANCE'); // BigInt — exact, no precision loss
Double-quoted values may span multiple lines, useful for things like PEM-encoded keys:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"
final env = Env.fromFile('.env');
print(env.getString('PRIVATE_KEY'));
Validate all required keys at startup so missing configuration fails fast:
final env = Env.fromPlatform();
env.require(['DB_HOST', 'DB_PORT', 'API_KEY']);
// throws EnvMissingKeysException listing every missing key
final env = Env({'DEBUG': 'true'});
env['DEBUG']; // "true"
env['MISSING']; // null
final env = Env.fromString('MSG="hello world"\nPATH=\'/usr/bin\'');
print(env.getString('MSG')); // hello world
print(env.getString('PATH')); // /usr/bin
Double-quoted values decode escape sequences (\n, \t, \r, \\, \"). Single-quoted values stay literal.
exportexport KEY=value lines (so the same file can be sourced from bash) are parsed as KEY=value:
final env = Env.fromString('export PORT=8080');
env.getInt('PORT'); // 8080
| Method | Description |
|---|---|
Env(Map<String, String> values) | Create an Env from a pre-parsed map |
Env.fromMap(Map<String, String> values) | Named constructor — alias for Env() |
Env.fromString(String content) | Parse a .env file content string |
Env.fromPlatform() | Load all variables from the process environment |
Env.fromFile(String path) | Load and parse a .env file from disk |
Env.fromFiles(List<String> paths) | Load and merge multiple .env files in priority order (later wins) |
getString(String key, {String? defaultValue}) | Get a string value |
getInt(String key, {int? defaultValue}) | Get an integer value |
getBigInt(String key, {BigInt? defaultValue}) | Get a BigInt value |
getBool(String key, {bool? defaultValue}) | Get a boolean (true/1/yes/on) |
getDouble(String key, {double? defaultValue}) | Get a double value |
getList(String key, {String separator, List<String>? defaultValue}) | Get a list by splitting on separator |
getUri(String key, {Uri? defaultValue}) | Get a parsed URI value |
getEnum<T>(String key, List<T> values, {T? defaultValue}) | Get an enum value (case-insensitive match) |
getDateTime(String key, {DateTime? defaultValue}) | Get an ISO 8601 / RFC 3339 timestamp |
getDuration(String key, {Duration? defaultValue}) | Get a duration with ms/s/m/h/d suffix (bare integer = ms) |
operator [](String key) | Raw nullable value access |
has(String key) | Check if a key exists |
require(Iterable<String> keys) | Throw if any required key is missing (lists all missing) |
keys | Get all available keys |
length / isEmpty / isNotEmpty | Collection ergonomics |
merge(Env other) | Combine with another Env (other wins on overlap) |
prefixed(String prefix, {bool stripPrefix = false}) | New Env with only keys starting with prefix |
filter(bool Function(String, String) predicate) | New Env containing only matching entries |
toMap() | Get all values as a map |
operator == / hashCode | Equality by underlying entries |
DotenvParser.parse(String content) | Parse .env content into a Map<String, String> |
dart pub get
dart analyze --fatal-infos
dart test
If you find this project useful: