penv

pub package license: MIT docs

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 export prefix (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 / getString accessors.
  • Sync (penvload), non-throwing (penvloadOrNull), and async (penvloadAsync) variants.
  • Optionally auto-creates a placeholder .env file (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 export is 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 expandVariables is true, ${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

MIT

Libraries

penv
A tiny, dependency-light loader for .env-style environment files.