Load environment variables from
.envfiles in Free Pascal β the easy way!
A feature-rich dotenv library for Free Pascal 3.2.2+ inspired by python-dotenv. Perfect for managing configuration in your Pascal applications without hardcoding sensitive data.
| Feature | Description |
|---|---|
π Load .env files |
Read configuration from .env files |
| π Variable interpolation | Use ${VAR} or $VAR syntax to reference other variables |
| π Multi-line values | Support for values spanning multiple lines |
| π― Quoted values | Single quotes, double quotes, or unquoted |
| π¬ Comments | Use # for comments (line or inline) |
| π Shell compatible | Supports export prefix for shell compatibility |
| π Type-safe getters | GetInt(), GetBool(), GetFloat(), GetArray() |
| β‘ Default values | Fallback values when keys are missing |
| β Validation | Check for required variables before running |
| π Multiple files | Load .env, .env.local, .env.production, etc. |
| π Environment-aware | Auto-load .env.{environment} files (v1.1.0+) |
| πΎ Save to file | Generate .env files programmatically (v1.1.0+) |
| π Generate examples | Create .env.example for version control (v1.1.0+) |
| π¬ Interactive prompts | GetOrPrompt() for first-run setup (v1.1.0+) |
| π·οΈ Key prefixing | Add prefixes like APP_ to all loaded keys |
| π§Ή Zero memory leaks | Uses advanced records β no manual Free calls! |
| π¦ Zero dependencies | Only standard FPC units |
- Copy
src/DotEnv.pasto your project
β or add thesrcfolder to your unit search path - Add
DotEnvto yourusesclause
That's it! No package manager needed. π
Create a .env file in your project root:
# Database settings
DATABASE_URL=postgresql://localhost/mydb
DB_POOL_SIZE=10
# Server configuration
PORT=3000
DEBUG=true
# Secrets (never commit these!)
SECRET_KEY="super-secret-key-here"program MyApp;
{$mode objfpc}{$H+}{$J-}
uses
DotEnv;
var
Env: TDotEnv;
begin
// Create and load .env file
Env := TDotEnv.Create;
Env.Load; // Loads .env from current directory
// Read values with type safety
WriteLn('Database: ', Env.Get('DATABASE_URL'));
WriteLn('Port: ', Env.GetInt('PORT', 3000));
WriteLn('Debug mode: ', Env.GetBool('DEBUG', False));
WriteLn('Pool size: ', Env.GetInt('DB_POOL_SIZE', 5));
// No need to free - advanced records clean up automatically!
end.# π¬ Comments start with #
# Empty lines are ignored
# Simple key-value pairs
DATABASE_URL=postgresql://localhost/mydb
PORT=3000
DEBUG=true
# π― Quoted values (preserves spaces and special chars)
SECRET_KEY="my-secret-key"
MESSAGE='Hello, World!'
GREETING="Welcome to the app!"
# π Variable interpolation
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1/users
FULL_URL=$BASE_URL/health
# π Multi-line values (use quotes)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
...more lines...
-----END RSA PRIVATE KEY-----"
# π Shell-compatible export syntax
export SHELL_VAR=works_in_bash_too
# π Arrays (comma-separated, parsed with GetArray)
ALLOWED_HOSTS=localhost,127.0.0.1,example.com
FEATURES=auth,logging,cachevar
Env: TDotEnv;
Options: TDotEnvOptions;
begin
// π’ Simple usage
Env := TDotEnv.Create;
Env.Load; // Load .env
Env.Load('.env.local'); // Load specific file
// π‘ With options
Options := TDotEnvOptions.Default;
Options.Override := True; // Override existing env vars
Options.Verbose := True; // Print debug info
Options.Prefix := 'APP_'; // Prefix all keys with APP_
Env := TDotEnv.CreateWithOptions(Options);
Env.Load;
// π΅ Load multiple files (later files override earlier ones)
Env.LoadMultiple(['.env', '.env.local', '.env.development']);
// π£ Load from string (great for testing!)
Env.LoadFromString('KEY=value' + LineEnding + 'OTHER=test');
// π NEW in v1.1.0: Environment-aware loading
Env.LoadForEnvironment('production'); // Loads .env + .env.production
Env.LoadForEnvironment(); // Auto-detects from APP_ENV or NODE_ENV
end;// π Strings
Env.Get('KEY'); // Returns '' if missing
Env.Get('KEY', 'default'); // Returns 'default' if missing
Env.GetRequired('KEY'); // Raises exception if missing
// π’ Integers
Env.GetInt('PORT'); // Returns 0 if missing/invalid
Env.GetInt('PORT', 3000); // Returns 3000 if missing/invalid
Env.GetIntRequired('PORT'); // Raises exception if missing/invalid
// β
Booleans (recognizes: true/false, yes/no, 1/0, on/off)
Env.GetBool('DEBUG'); // Returns False if missing
Env.GetBool('DEBUG', True); // Returns True if missing
Env.GetBoolRequired('DEBUG'); // Raises exception if missing
// π¬ Floats
Env.GetFloat('RATE'); // Returns 0.0 if missing/invalid
Env.GetFloat('RATE', 0.5); // Returns 0.5 if missing/invalid
Env.GetFloatRequired('RATE'); // Raises exception if missing/invalid
// π Arrays (splits comma-separated values)
Hosts := Env.GetArray('ALLOWED_HOSTS'); // Split by comma
Tags := Env.GetArray('TAGS', ';'); // Split by semicolon
// π¬ NEW in v1.1.0: Interactive prompts (for setup scripts)
DbUrl := Env.GetOrPrompt('DATABASE_URL',
'Enter database URL',
'postgres://localhost/mydb');
// Prompts user if DATABASE_URL is missing, uses existing value if presentvar
Missing: TStringDynArray;
I: Integer;
begin
// β
Check if all required keys exist
if Env.Validate(['DATABASE_URL', 'SECRET_KEY', 'PORT']) then
WriteLn('All required configuration present!')
else
begin
// π Get list of missing keys
Missing := Env.GetMissing(['DATABASE_URL', 'SECRET_KEY', 'PORT']);
WriteLn('Missing configuration:');
for I := 0 to High(Missing) do
WriteLn(' - ', Missing[I]);
Halt(1);
end;
end;// π Check if key exists
if Env.Has('OPTIONAL_FEATURE') then
EnableFeature;
// π Get all keys and values
Keys := Env.Keys; // TStringDynArray of all key names
Values := Env.Values; // TStringDynArray of all values
Pairs := Env.AsArray; // TDotEnvPairArray with Key/Value records
// π’ Count loaded variables
WriteLn('Loaded ', Env.Count, ' environment variables');
// π Debug output (shows all loaded key=value pairs)
WriteLn(Env.ToString);
// π See which files were loaded
for I := 0 to High(Env.LoadedFiles) do
WriteLn('Loaded: ', Env.LoadedFiles[I]);// πΎ Save environment variables to a file
Env := TDotEnv.Create;
Env.SetToEnv('DATABASE_URL', 'postgres://localhost/mydb');
Env.SetToEnv('PORT', '3000');
Env.SetToEnv('DEBUG', 'true');
Env.Save('.env'); // Writes to .env file
// π Generate .env.example for version control
Env.Load('.env');
Env.GenerateExample('.env', '.env.example');
// Creates .env.example with keys but empty values
// π Environment-aware loading pattern
Env := TDotEnv.Create;
Env.LoadForEnvironment('development'); // Loads .env + .env.development
Env.LoadForEnvironment('production'); // Loads .env + .env.production
Env.LoadForEnvironment(); // Auto-detects from APP_ENV/NODE_ENV
// π¬ Interactive setup script example
Env := TDotEnv.Create;
Env.Load('.env'); // Try to load existing config
DbUrl := Env.GetOrPrompt('DATABASE_URL',
'Enter database URL',
'postgres://localhost/mydb');
Port := Env.GetOrPrompt('PORT', 'Enter port', '3000');
Env.Save('.env'); // Save the configuration
WriteLn('Configuration saved!');For quick scripts or simple applications:
uses DotEnv;
begin
DotEnvLoad; // Load .env
DotEnvLoad('.env.local'); // Load specific file
WriteLn(DotEnvGet('DATABASE_URL')); // Get value
WriteLn(DotEnvGet('PORT', '3000')); // Get with default
DotEnvSet('RUNTIME_VAR', 'value'); // Set at runtime
end.Reference other variables using ${VAR} or $VAR syntax:
# Define base values
APP_NAME=MyAwesomeApp
APP_VERSION=1.0.0
BASE_URL=https://api.example.com
# Reference them in other values
APP_TITLE=${APP_NAME} v${APP_VERSION}
API_USERS=${BASE_URL}/users
API_HEALTH=$BASE_URL/health
# Also works with system environment variables!
HOME_CONFIG=${HOME}/.myapp/configResolution order:
- Variables defined earlier in the same
.envfile - System environment variables
| Option | Type | Default | Description |
|---|---|---|---|
Override |
Boolean |
False |
Override existing system environment variables |
Interpolate |
Boolean |
True |
Enable ${VAR} variable interpolation |
Verbose |
Boolean |
False |
Print debug info while loading |
Prefix |
String |
'' |
Add prefix to all loaded key names |
var
Options: TDotEnvOptions;
begin
Options := TDotEnvOptions.Default; // Start with defaults
Options.Override := True;
Options.Prefix := 'MYAPP_';
Env := TDotEnv.CreateWithOptions(Options);
Env.Load;
// KEY=value in .env becomes accessible as MYAPP_KEY
WriteLn(Env.Get('MYAPP_KEY'));
end;uses DotEnv;
var
Env: TDotEnv;
begin
Env := TDotEnv.Create;
Env.Load;
// π΄ Handle missing required keys
try
Env.GetRequired('MISSING_KEY');
except
on E: EDotEnvMissingKey do
WriteLn('Configuration error: ', E.Message);
end;
// π΄ Handle invalid type conversions
try
Env.GetIntRequired('NOT_A_NUMBER');
except
on E: EDotEnvParseError do
WriteLn('Parse error: ', E.Message);
end;
end.dotenv-fp is inspired by python-dotenv and provides equivalent core functionality, plus additional features suited for statically-typed Pascal development:
| Feature | python-dotenv | dotenv-fp |
|---|---|---|
| Load .env file | β | β |
| Variable interpolation | β | β |
| Multi-line values | β | β |
| Quoted values | β | β |
| Export prefix | β | β |
| Comments | β | β |
| Override mode | β | β |
| Type-safe getters | β | β |
| Built-in validation | β | β |
| Array parsing | β | β |
| Key prefixing | β | β |
| Automatic memory management | N/A | β |
The library includes a comprehensive test suite using FPCUnit:
cd tests
fpc TestRunner.pas
./TestRunner -a --format=plainExpected output:
Time:00.075 N:96 E:0 F:0 I:0
TTestDotEnv Time:00.075 N:96 E:0 F:0 I:0
00.000 Test01_BasicParsing_SimpleKeyValue
00.000 Test02_BasicParsing_TrimmedValue
...
Number of run tests: 96
Number of errors: 0
Number of failures: 0
dotenv-fp/
βββ src/
β βββ DotEnv.pas # Main library unit
βββ tests/
β βββ TestRunner.pas # FPCUnit test runner
β βββ DotEnv.Test.pas # Test cases
βββ examples/
β βββ basic/ # Basic usage example
β βββ advanced/ # Advanced features example
βββ docs/ # Documentation
βββ .env.example # Example .env file
βββ CHANGELOG.md
βββ README.md
-
Advanced Records: This library uses advanced records (
{$modeswitch advancedrecords}), which means you don't need to call.Freeβ memory is managed automatically! -
Mode ObjFPC: Make sure your program uses
{$mode objfpc}{$H+}{$J-}for compatibility. -
String Handling: The
{$H+}switch enables long strings (AnsiString) by default, which this library requires. -
File Paths: Use forward slashes
/or thePathDelimconstant for cross-platform compatibility.
Contributions are welcome! Feel free to:
- π Report bugs
- π‘ Suggest features
- π§ Submit pull requests
MIT License β See LICENSE file for details.
- Inspired by python-dotenv
- Built for the awesome Free Pascal community
Happy coding! π