dotenv.net is a lightweight library for loading environment variables from .env files in .NET applications. It
keeps sensitive configuration out of source code and supports a range of options to suit different project setups.
Install via NuGet using any of the following methods:
.NET CLI
dotnet add package dotenv.netPackage Manager Console
Install-Package dotenv.netPackageReference
<PackageReference Include="dotenv.net" Version="4.x.x"/>using dotenv.net;
// Load .env from the application directory into the system environment
DotEnv.Load();
// Read variables from .env without modifying the system environment
var envVars = DotEnv.Read();
Console.WriteLine(envVars["DATABASE_URL"]);By default, both methods look for a .env file in the same directory as the application executable.
Both Load() and Read() accept a DotEnvOptions instance to customise behaviour.
DotEnv.Load(options: new DotEnvOptions(
ignoreExceptions: false, // Throw on errors instead of silently failing (default: true)
envFilePaths: ["./config/.env"], // One or more paths to .env files (default: [".env"])
encoding: Encoding.UTF8, // File encoding (default: UTF-8)
trimValues: true, // Strip whitespace from values (default: false)
overwriteExistingVars: false, // Skip vars already set in the environment (default: true)
probeForEnv: true, // Search parent directories for a .env file (default: false)
probeLevelsToSearch: 3, // How many directory levels to ascend when probing (default: 4)
supportExportSyntax: true, // Support `export KEY=VALUE` syntax (default: false)
supportInlineComments: true, // Strip `# comment` from unquoted values (default: true)
supportVariableExpansion: true, // Expand ${VAR} and $VAR variable references (default: true)
environmentCascade: true, // Load layered .env / .env.local / .env.{Environment} files (default: false)
environmentName: "Development" // Optional explicit environment for cascade loading
));Note:
probeForEnvand customenvFilePathsare mutually exclusive.environmentCascadecannot be combined with customenvFilePathsorenvStreams.environmentCascadecan be used together withprobeForEnv. Setting conflicting options will throw anInvalidOperationException.
DotEnv.Load(options: new DotEnvOptions(
envFilePaths: ["./config/.env", "./secrets/.env"]
));When overwriteExistingVars is false, keys from earlier files take precedence over those in later files.
Opt-in hierarchical loading resolves layered .env files from the same directory as .env (or from the probed directory when probeForEnv is enabled). Later files override earlier files when overwriteExistingVars is true (the default):
.env.env.local.env.{Environment}(only when an environment name is known).env.{Environment}.local(only when an environment name is known)
The environment name is resolved in this order: an explicit name passed to WithEnvironmentCascade, then ASPNETCORE_ENVIRONMENT, DOTNET_ENVIRONMENT, and DOTENV_ENV. If none are set, only .env and .env.local are considered. Missing files in the cascade are skipped. Add *.local to .gitignore so machine-specific secrets are not committed.
DotEnv.Fluent()
.WithEnvironmentCascade() // uses ASPNETCORE_ENVIRONMENT / DOTNET_ENVIRONMENT / DOTENV_ENV
.Load();
DotEnv.Fluent()
.WithEnvironmentCascade("Production") // explicit environment name
.WithProbeForEnv() // cascade + probe is allowed
.Load();WithEnvironmentCascade() cannot be combined with custom WithEnvFiles(...) paths or WithEnvStreams(...).
DotEnv.Fluent() returns a DotEnvOptions instance that exposes a chainable builder API, useful when you prefer an
explicit, readable configuration style.
Loading variables:
DotEnv.Fluent()
.WithExceptions()
.WithEnvFiles("./config/.env")
.WithTrimValues()
.WithEncoding(Encoding.UTF8)
.WithOverwriteExistingVars()
.WithProbeForEnv(probeLevelsToSearch: 6)
.WithSupportExportSyntax()
.Load();Reading variables without writing to the environment:
var envVars = DotEnv.Fluent()
.WithoutExceptions()
.WithEnvFiles() // Defaults to .env
.WithoutTrimValues()
.WithEncoding(Encoding.UTF8)
.WithoutOverwriteExistingVars()
.WithoutProbeForEnv()
.WithoutSupportExportSyntax()
.Read();| Method | Description |
|---|---|
WithExceptions() |
Throw exceptions on errors |
WithoutExceptions() |
Silently ignore errors (default) |
WithEnvFiles(params string[]) |
Specify one or more .env file paths |
WithEncoding(Encoding) |
Set file encoding |
WithTrimValues() |
Strip whitespace from values |
WithoutTrimValues() |
Preserve whitespace in values (default) |
WithOverwriteExistingVars() |
Overwrite existing environment variables (default) |
WithoutOverwriteExistingVars() |
Preserve existing environment variables |
WithProbeForEnv(int) |
Search parent directories for a .env file |
WithoutProbeForEnv() |
Disable parent directory search (default) |
WithSupportExportSyntax() |
Support export KEY=VALUE syntax |
WithoutSupportExportSyntax() |
Disable export syntax support (default) |
WithSupportInlineComments() |
Strip # comment from unquoted values (default) |
WithoutSupportInlineComments() |
Preserve inline comments in values |
WithSupportVariableExpansion() |
Enable variable expansion and interpolation |
WithoutSupportVariableExpansion() |
Disable variable expansion and interpolation (default) |
WithVariableExpansion() |
Alias for WithSupportVariableExpansion() |
WithoutVariableExpansion() |
Alias for WithoutSupportVariableExpansion() |
WithEnvironmentCascade(string?) |
Load .env, .env.local, and environment-specific files |
WithoutEnvironmentCascade() |
Disable hierarchical loading (default) |
dotenv.net supports variable expansion (substitution), allowing values to reference other variables or system environment settings using ${VAR} or $VAR syntax:
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1
PORT=8080
DATABASE_URL=postgres://${USER}:${PASSWORD}@localhost:${PORT}/db- Braced variables:
${VAR}expands to the value ofVAR. - Short variables:
$VARexpandsVARmatching[A-Za-z_][A-Za-z0-9_]*. Dollar signs not followed by a valid identifier (e.g.$100or a trailing$) are preserved literally. - Default fallbacks:
${VAR:-default}: Evaluates todefaultifVARis unset or empty.${VAR-default}: Evaluates todefaultifVARis unset (preserves an explicitly set empty value).
- Nested expressions: Fallbacks can be nested, e.g.
${CUSTOM_URL:-${DEFAULT_HOST:-localhost}:3000}. - Resolution hierarchy: Variables are resolved sequentially in document order against earlier parsed keys, cascading to
System.Environmentif not set in the.envfile. Missing variables without a default resolve to an empty string.
- Single quotes (
'...'): Treated as raw literals. Variables inside single quotes are never expanded (e.g.'${NOT_EXPANDED}'remains literal${NOT_EXPANDED}). - Double quotes (
"...") and unquoted values: Variable expansion is enabled. - Backslash escaping: Preceding a dollar sign with a backslash prevents expansion (e.g.
\${VAR}evaluates to${VAR}, and\$VARevaluates to$VAR).
Circular references (such as direct A=${A} or indirect A=${B}, B=${A}) are detected automatically:
- When
ignoreExceptionsisfalse, anInvalidOperationExceptionis thrown identifying the cycle path. - When
ignoreExceptionsistrue, the cyclic reference resolves safely to an empty string without crashing or causing a stack overflow.
To enable variable expansion:
DotEnv.Fluent()
.WithVariableExpansion()
.Load();DotEnv.Read() returns an IDictionary<string, string> of the parsed key-value pairs without writing anything to the
system environment. This is useful for inspecting values or selectively applying them.
var envVars = DotEnv.Read();
if (envVars.TryGetValue("API_KEY", out var apiKey))
{
// use apiKey
}The dotenv.net.Utilities namespace provides EnvReader, a helper class for reading strongly-typed values directly
from the system environment (i.e., after calling DotEnv.Load()).
using dotenv.net.Utilities;
var host = EnvReader.GetStringValue("DB_HOST");
var port = EnvReader.GetIntValue("DB_PORT");
var enabled = EnvReader.GetBooleanValue("FEATURE_FLAG");For non-throwing alternatives, use the TryGet* methods:
if (EnvReader.TryGetIntValue("DB_PORT", out var port))
{
// port is valid
}| Method | Return Type | Throws if missing |
|---|---|---|
HasValue(string key) |
bool |
No |
GetStringValue(string key) |
string |
Yes |
GetIntValue(string key) |
int |
Yes |
GetDoubleValue(string key) |
double |
Yes |
GetDecimalValue(string key) |
decimal |
Yes |
GetBooleanValue(string key) |
bool |
Yes |
TryGetStringValue(string key, out string value) |
bool |
No, returns null on failure |
TryGetIntValue(string key, out int value) |
bool |
No, returns 0 on failure |
TryGetDoubleValue(string key, out double value) |
bool |
No, returns 0.0 on failure |
TryGetDecimalValue(string key, out decimal value) |
bool |
No, returns 0.0m on failure |
TryGetBooleanValue(string key, out bool value) |
bool |
No, returns false on failure |
Contributions are welcome. If you have a bug report, feature request, or improvement in mind, please open an issue or submit a pull request.
To get started:
- Fork the repository and create a feature branch.
- Make your changes and ensure existing tests still pass.
- Add tests for any new behaviour.
- Open a pull request with a clear description of what was changed and why.
The project targets .NET and uses the standard dotnet CLI toolchain. Run the test suite with:
dotnet testThanks to everyone who has contributed to dotenv.net:
@bolorundurowb @joliveros @vizeke @merqlove @tracker1 @NaturalWill @texyh @jonlabelle @Gounlaf @DTTerastar @Mondonno @caveman-dick @VijoPlays @bobbyg603 @Moha-sami
dotenv.net is licensed under the MIT License. See the LICENSE file for details.
Happy Coding! π