DotEnv.jl is a lightweight package that loads environment variables from .env files into ENV. Storing configuration in the environment is based on The Twelve-Factor App methodology.

Please don't store secrets in dotenv files, and if you must at least ensure the dotenv file(s) are listed in .gitignore.


DotEnv: load!(), unload!(), config, parse.


Use DotEnv.load!() to load environment variables, DotEnv.unload!() to undo the loading, and DotEnv.config() to fetch a dictionary representing the modified environment without mutating it.

See the docstrings of these functions for more information.

Automatically detected dotenv files

DotEnv matches the canonical behaviour of, and will read the following filenames, with the most specific (last) taking priority:

  • .env
  • .env.production
  • .env.test
  • .env.development
  • .env.local
  • .env.production.local
  • .env.test.local
  • .env.development.local

If there are no dotenv files in the current directory, DotEnv.load!() and DotEnv.unload!() will look at the parent directory, recursively until dotenv files are found or the root directory is reached.

DotEnv format

The DotEnv format will likely be familiar.

FOO=bar # inline comment

For the sake of clarity though, the parsing rules are thus:

  1. Leading whitespace is ignored
  2. Empty lines, and lines starting with # are skipped
  3. export prefixes are ignored (e.g. export FOO=bar)
  4. Empty assignments are regarded as the empty string(EMPTY= becomes "EMPTY" => "")
  5. Keys and values are separated by = or :, and any amount of whitespace
  6. All values are strings, but can be quoted with single (') or double quotes (")
  7. Quotes of the same type may occur within quoted values, but must be escaped with \
  8. Inline comments are started with a # outside of a quoted value
  9. Inside double quoted values, \n newlines are expanded, allowing for multiline values
  10. Extra spaces are removed from both ends of an unquoted value (e.g. FOO some value becomes"FOO" => "some value"`)
  11. Variable expansion occurs within unquoted and double-quoted values. $NAME, ${NAME}, and ${NAME:-default} expansions are all supported.
  12. Malformed lines are silently skipped
ENV_FILENAMES :: Vector{String}

A list of dotenv file (base)names that should be automatically loaded, from least to most authoratative.

ENV_ORIGINALS :: IdDict{AbstractDict{String, String}, Dict{String, Union{String, Nothing}}}

A record of the original values of a particular environment dict.

This is used to record overwritten values, so the original values can be restored later.

ENV_STACKS :: IdDict{AbstractDict{String, String}, Vector{EnvFile}}

The accumulation of dotenv files applied to environment dicts.

This record allows for reasoning of what would happen if a particular environment file was never loaded, for example.

struct EnvEntry

A primitive representation of a single entry of a dotenv file.

It is primitive in the sense that the value is untransformed, no interpolation has been performed.

See also: loadexpand!, _parse.

struct EnvFile

A representation of all of the entries in a particular dotenv file, along with whether it should overwrite existing values or not.

struct EnvOverlay{B <: AbstractDict{String, String}} <: AbstractDict{String, String}

A wrapper around a base environment dictionary, that overlays new/changed values.

_parse(source::Union{IO, AbstractString, AbstractVector{UInt8}})

Parse the .env content source into a vector of Pair{String, Tuple{String, Bool}} key-value-interpolate pairs.

config(src::IO; env::AbstractDict{String, String} = ENV)
config(path::AbstractString = ".env"; env::AbstractDict{String, String} = ENV)

Read the dotenv file src/path and return an EnvOverlay of its values, expanding interpolated values with env.

Should the file path exist, an empty EnvOverlay is silently returned.

interpolate(value::String, dotenv::Dict{String, String}, fallback::AbstractDict{String, String})

Expand interpolations in value, returning the final result. Interpolated values can be in the form $name, ${name}, or ${name:-default} (with nesting allowed within default). Values are looked up in dotenv then fallback, with the empty string used if not present in either.

load!([env::AbstractDict{String, String}=ENV], path::AbstractString = ""; override::Bool=false)

Load the dotenv file path, or should path be a directory every ENV_FILENAME that lies within it, into env.

Should override be set, values already present in env will be replaced with statements from the dotenv file(s).

loadexpand!(dotenv::Dict{String, String}, entry::EnvEntry, fallback::AbstractDict{String, String}=ENV)

Modify dotenv to incorperate on entry, which will be interpolated (if appropriate) using dotenv and fallback.

parse(source::Union{IO, AbstractString, AbstractVector{UInt8}})

Parse the .env content source into a Dict{String, String}.


Try to parse line according to the format introduced by

Returns a Pair{String, String} if parsing was successful, nothing otherwise.


Try to extract the possibly quoted value from valstring, which may be succeeded by a #-comment. Returns a tuple of the value and a Bool indicating whether or not the value should be interpolated.

unload!(env::AbstractDict{String, String}, path::AbstractString)
unload!(ENV, path::AbstractString = "")

Unload the dotenv file path from env, or should path be a directory every ENV_FILENAMES that lies within it.

When env is omitted, ENV is used and a path defaults to the current directory.

unload!(env::AbstractDict{String, String})

Undo all dotenv modifications to env.