penv
A tiny loader for .env-style environment files, written in pure Dart.
penv reads a simple KEY=value file from disk and returns it as a Map<String, String>. If the file doesn't exist yet, it can generate a placeholder for you and tell you where to fill it in.
Features
- Supports comments (
#) and blank lines. - Supports a leading
exportprefix (as used by shell-sourced env files). - Strips optional single or double quotes around values, including
backslash escapes (
\n,\t,\r,\",\\,\$) inside double-quoted values. - Strips trailing inline comments on unquoted values (
PORT=8080 # default). - Expands
${OTHER_KEY}references against earlier keys in the same file or the process environment. - Optionally overlays real process environment variables onto matching keys, so deployment env vars can take priority over a checked-in file.
- Optional required-key validation with a clear error listing what's missing.
- Typed
getInt/getDouble/getBool/getStringaccessors. - Sync (
penvload), non-throwing (penvloadOrNull), and async (penvloadAsync) variants. - Optionally auto-creates a placeholder
.envfile (with your own template) on first run.
Installation
Add penv to your pubspec.yaml:
dependencies:
penv: ^2.0.0
Then run:
dart pub get
Usage
Create a .env file in your project root:
API_KEY=your-key-here
BASE_URL="https://example.com"
GREETING='hello world'
Load it:
import 'package:penv/penv.dart';
void main() {
final env = penvload('.env');
print(env['API_KEY']); // your-key-here
print(env['BASE_URL']); // https://example.com
print(env['GREETING']); // hello world
}
Handling a missing file
By default, if the file at path doesn't exist, penvload creates it with a
placeholder template and throws EnvFileNotFoundException so you notice and
fill it in:
import 'package:penv/penv.dart';
void main() {
try {
final env = penvload('.env');
print(env['API_KEY']);
} on EnvFileNotFoundException catch (e) {
print(e); // tells you a template file was created at the given path
}
}
You can supply your own placeholder content via template:
penvload(
'.env',
template: '''
# App configuration
API_KEY=
BASE_URL=
''',
);
If you'd rather penv not touch the filesystem when the file is missing,
pass createFile: false. In that case no file is written, and
EnvFileNotFoundException is thrown immediately:
penvload('.env', createFile: false);
If a missing file is actually fine (e.g. an optional local override file),
use penvloadOrNull instead — it returns null rather than throwing, and
never writes a placeholder:
final overrides = penvloadOrNull('.env.local') ?? <String, String>{};
export, inline comments, and escapes
export API_KEY=your-key-here # `export ` is stripped automatically
PORT=8080 # inline comments after unquoted values are stripped
GREETING="Hello\nWorld" # \n, \t, \r, \", \\, \$ are unescaped
LITERAL='no \n unescaping here' # single-quoted values are literal
final env = penvload('.env');
print(env['GREETING']); // Hello
// World
Variable expansion
${OTHER_KEY} references inside unquoted or double-quoted values are
expanded using keys defined earlier in the same file, falling back to the
current process environment. Unresolved references expand to an empty
string. Single-quoted values are never expanded.
BASE_URL=https://example.com
FULL_URL=${BASE_URL}/api
final env = penvload('.env');
print(env['FULL_URL']); // https://example.com/api
Pass expandVariables: false to disable this and treat ${...} as
literal text.
Overlaying the process environment
Real deployments (Docker, CI, hosting platforms) often set environment
variables directly instead of shipping a .env file. Pass
useProcessEnvironment: true to let matching process environment
variables override (or, with preferProcessEnvironment: false, be
overridden by) values from the file. Only keys already declared in the
file are considered — unrelated system variables like PATH are never
pulled in.
final env = penvload(
'.env',
useProcessEnvironment: true, // process env wins over the file by default
);
Required keys
Pass required to make sure specific keys are present (and non-empty)
after parsing. If any are missing, MissingRequiredKeysException is
thrown listing every missing key at once:
final env = penvload('.env', required: ['API_KEY', 'BASE_URL']);
Typed accessors
The returned map has getInt, getDouble, getBool, and getString
extension methods that parse a value (with an optional defaultValue)
and throw a clear FormatException instead of a null-check crash or a
silent int.parse error:
final env = penvload('.env');
final port = env.getInt('PORT', defaultValue: 8080);
final debug = env.getBool('DEBUG', defaultValue: false);
final apiKey = env.getString('API_KEY'); // throws if missing/blank
getBool accepts (case-insensitively) true/false, 1/0,
yes/no, and on/off.
Async loading
penvloadAsync mirrors penvload but avoids synchronous file I/O:
final env = await penvloadAsync('.env');
See the example/ directory for runnable scripts covering the
basic flow, the advanced parsing/typed-accessor features, process
environment overlay, and async loading.
API
penvload(String path, {String template, bool createFile, bool expandVariables, bool useProcessEnvironment, bool preferProcessEnvironment, List<String>? required})
Reads path and returns a Map<String, String> of the parsed key-value
pairs.
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
String |
required | Path to the env file to read. |
template |
String |
a built-in placeholder | Content written to path if it doesn't exist and createFile is true. |
createFile |
bool |
true |
Whether to create a placeholder file when path doesn't exist. |
expandVariables |
bool |
true |
Whether to expand ${OTHER_KEY} references. |
useProcessEnvironment |
bool |
false |
Whether to overlay Platform.environment onto keys already declared in the file. |
preferProcessEnvironment |
bool |
true |
When overlaying, whether the process environment wins over the file's value. |
required |
List<String>? |
null |
Keys that must be present with a non-empty value, or MissingRequiredKeysException is thrown. |
Throws EnvFileNotFoundException if no file exists at path.
Throws MissingRequiredKeysException if required keys are missing.
penvloadOrNull(String path, {bool expandVariables, bool useProcessEnvironment, bool preferProcessEnvironment, List<String>? required})
Like penvload, but returns null instead of throwing when path
doesn't exist, and never creates a placeholder file. Still throws
MissingRequiredKeysException if the file exists but is missing
required keys.
penvloadAsync(String path, {...})
Async equivalent of penvload, with the same parameters and thrown
exceptions.
Parsing rules
- Lines are trimmed before processing.
- Empty lines and lines starting with
#are ignored. - A leading
exportis stripped before the key is parsed. - Lines without an
=are ignored. - The key is trimmed; empty keys are ignored.
- The value is trimmed; if it's fully wrapped in matching
'or"quotes, the quotes are stripped. - Inside double-quoted values,
\n,\t,\r,\",\\, and\$are unescaped. Single-quoted values are literal. - Unquoted values have a trailing
# comment(or\t# comment) stripped. - If
expandVariablesistrue,${OTHER_KEY}in unquoted or double-quoted values is replaced by that key's value (from earlier in the file, then the process environment), or an empty string if unresolved. - If duplicate keys appear, the last one in the file wins.
EnvFileNotFoundException
Thrown by penvload and penvloadAsync when path doesn't exist.
| Property | Type | Description |
|---|---|---|
path |
String |
The path that was looked up. |
createFile |
bool |
Whether a placeholder file was written to path before throwing. |
MissingRequiredKeysException
Thrown by penvload, penvloadOrNull, and penvloadAsync when one or
more required keys are missing or blank after parsing.
| Property | Type | Description |
|---|---|---|
keys |
List<String> |
The keys that were missing or blank. |
Contributing
Issues and pull requests are welcome at psdkjoon/penv.
License
Libraries
- penv
- A tiny, dependency-light loader for
.env-style environment files.