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! ๐