Overview
Dotenv C++ is a lightweight, cross-platform library that allows you to load environment variables from .env files
into your application. Designed for runtime DLL/shared library usage with thread-safe operations.
📁 Simple File Format
Uses standard .env file format with key=value pairs
🔤 Quoted Values
Support for single and double quoted string values
🌐 Cross-Platform
Works on Windows, Linux, and macOS systems
📦 Shared Library
Distributed as DLL/SO/DYLIB for easy integration
🔌 Multiple APIs
C++ and C interfaces available
⚡ Dynamic Loading
Runtime DLL loading supported
🔒 Thread-Safe
Mutex-protected operations for concurrent access
⚠️ Error Handling
Proper error codes and messages
💬 Inline Comments
Support for comments after values
🔄 Escape Sequences
Support for \n, \t, \\ escape sequences
✂️ Whitespace Trimming
Automatic trimming of keys and values
🔧 CMake Support
Modern CMake and Makefile build systems
Installation
Download
Download the latest release from the GitHub releases page.
Files by Platform
| Platform | Library | Import Library |
|---|---|---|
| Windows | dotenv.dll |
libdotenv.a |
| Linux | libdotenv.so |
- |
| macOS | libdotenv.dylib |
- |
Quick Setup
- Copy the shared library to your project's library directory
- Copy
dotenv.hppto your project's include directory - Link against the library in your build configuration
Requirements
- C++17 compatible compiler (g++, clang++, MSVC)
- CMake 3.10+ or GNU Make
Usage
1. Create a .env file
# .env file
DATABASE_URL=postgresql://localhost/mydb
API_KEY="your-api-key-here"
SECRET='single-quoted-value'
PORT=3000
DEBUG=true
# Inline comments
HOST=localhost # This is the server host
# Escape sequences (in double quotes)
MESSAGE="Line1\nLine2\tTabbed"
# Whitespace is trimmed
KEY_WITH_SPACES = value with spaces trimmed
2. C++ API
The modern C++ API with error handling:
#include "dotenv.hpp"
#include <iostream>
int main() {
// Load environment variables from .env
DotenvError result = Dotenv::load(".env");
if (result != DotenvError::Success) {
std::cerr << "Error: " << Dotenv::getLastError() << std::endl;
return 1;
}
// Get environment variables with default values
std::string dbUrl = Dotenv::get("DATABASE_URL", "localhost");
std::string apiKey = Dotenv::get("API_KEY");
// Check if a variable exists
if (Dotenv::has("DEBUG")) {
std::cout << "Debug mode enabled" << std::endl;
}
// Get all loaded keys
auto keys = Dotenv::getLoadedKeys();
for (const auto& key : keys) {
std::cout << "Loaded: " << key << std::endl;
}
// Clear all loaded variables
Dotenv::clear();
return 0;
}
3. C API
For C compatibility and runtime DLL loading:
#include "dotenv.hpp"
#include <stdio.h>
int main() {
// Load environment variables
int result = DotenvLoad(".env");
if (result != 0) {
printf("Error: %s\n", DotenvGetLastError());
return 1;
}
// Get variables with default value
const char* port = DotenvGet("PORT", "8080");
printf("Port: %s\n", port);
// Check if variable exists
if (DotenvHas("DEBUG")) {
printf("Debug mode enabled\n");
}
// Check if loaded
if (DotenvIsLoaded()) {
printf("Environment loaded successfully\n");
}
// Clear loaded variables
DotenvClear();
return 0;
}
4. Dynamic Runtime Loading (Windows)
Load the DLL at runtime without static linking:
#include <windows.h>
#include <iostream>
typedef int (*DotenvLoadFunc)(const char*);
typedef const char* (*DotenvGetFunc)(const char*, const char*);
typedef int (*DotenvHasFunc)(const char*);
int main() {
// Load the DLL at runtime
HMODULE hDll = LoadLibrary("dotenv.dll");
if (!hDll) {
std::cerr << "Failed to load dotenv.dll" << std::endl;
return 1;
}
// Get function pointers
auto loadFunc = (DotenvLoadFunc)GetProcAddress(hDll, "DotenvLoad");
auto getFunc = (DotenvGetFunc)GetProcAddress(hDll, "DotenvGet");
auto hasFunc = (DotenvHasFunc)GetProcAddress(hDll, "DotenvHas");
if (loadFunc && getFunc) {
loadFunc(".env");
std::cout << "API Key: " << getFunc("API_KEY", "") << std::endl;
if (hasFunc && hasFunc("DEBUG")) {
std::cout << "Debug mode enabled" << std::endl;
}
}
// Clean up
FreeLibrary(hDll);
return 0;
}
5. Dynamic Runtime Loading (Linux/macOS)
Load the shared library at runtime on Unix systems:
#include <dlfcn.h>
#include <iostream>
typedef int (*DotenvLoadFunc)(const char*);
typedef const char* (*DotenvGetFunc)(const char*, const char*);
typedef int (*DotenvHasFunc)(const char*);
int main() {
// Load the shared library
void* handle = dlopen("./libdotenv.so", RTLD_LAZY); // Use .dylib on macOS
if (!handle) {
std::cerr << "Failed to load library: " << dlerror() << std::endl;
return 1;
}
// Get function pointers
auto loadFunc = (DotenvLoadFunc)dlsym(handle, "DotenvLoad");
auto getFunc = (DotenvGetFunc)dlsym(handle, "DotenvGet");
auto hasFunc = (DotenvHasFunc)dlsym(handle, "DotenvHas");
if (loadFunc && getFunc) {
loadFunc(".env");
std::cout << "API Key: " << getFunc("API_KEY", "") << std::endl;
if (hasFunc && hasFunc("DEBUG")) {
std::cout << "Debug mode enabled" << std::endl;
}
}
// Clean up
dlclose(handle);
return 0;
}
.env File Format
- Comments start with
# - Key-value pairs:
KEY=value - Double quoted values:
KEY="value with spaces" - Single quoted values:
KEY='value' - Inline comments:
KEY=value # comment - Escape sequences:
\n,\t,\\ - Whitespace trimming on keys and values
- Empty lines are ignored
# Database Configuration
DATABASE_URL=postgresql://localhost:5432/mydb
DB_USER=admin
DB_PASSWORD="secure_password_123"
# API Settings
API_KEY="sk-1234567890abcdef"
API_ENDPOINT=https://api.example.com
# Application Settings
PORT=3000
DEBUG=true
LOG_LEVEL=info
# Escape sequences example
MULTILINE="Line 1\nLine 2\nLine 3"
API Reference
C++ API (Dotenv class)
Dotenv::load()
static DotenvError Dotenv::load(const std::string& filename = ".env");
static DotenvError Dotenv::load(const std::string& filename, const DotenvOptions& options);
Description: Loads environment variables from the specified file.
| Parameter | Type | Description |
|---|---|---|
| filename | const std::string& | Path to the .env file (default: ".env") |
| options | const DotenvOptions& | Optional configuration for loading behavior |
Returns: DotenvError enum value indicating success or failure.
Dotenv::get()
static std::string Dotenv::get(const std::string& key, const std::string& defaultValue = "");
Description: Retrieves the value of an environment variable.
| Parameter | Type | Description |
|---|---|---|
| key | const std::string& | The name of the environment variable |
| defaultValue | const std::string& | Value to return if variable doesn't exist (default: "") |
Returns: The value of the variable or the default value.
Dotenv::has()
static bool Dotenv::has(const std::string& key);
Description: Checks if an environment variable exists.
Returns: true if the variable exists, false otherwise.
Dotenv::getLoadedKeys()
static std::vector<std::string> Dotenv::getLoadedKeys();
Description: Returns a list of all keys that were loaded from the .env file.
Returns: Vector of key names.
Dotenv::clear()
static void Dotenv::clear();
Description: Clears all environment variables that were loaded by the library.
Dotenv::isLoaded()
static bool Dotenv::isLoaded();
Description: Checks if any .env file has been successfully loaded.
Returns: true if loaded, false otherwise.
Dotenv::getLastError()
static std::string Dotenv::getLastError();
Description: Gets the error message from the last operation.
Returns: Error message string.
DotenvOptions Structure
struct DotenvOptions {
bool overwrite = true; // Overwrite existing env vars
bool interpolate = false; // Interpolate variables ${VAR}
bool stripQuotes = true; // Strip quotes from values
bool trimWhitespace = true; // Trim whitespace from keys and values
};
Error Codes (DotenvError)
| Code | Value | Description |
|---|---|---|
Success |
0 | Operation completed successfully |
FileNotFound |
1 | Could not open the specified file |
ParseError |
2 | Error parsing the file contents |
InvalidKey |
3 | Invalid key format encountered |
C API Functions
DotenvGet and DotenvGetLastError
are valid until the next call to the same function. Copy the string immediately if you need to preserve the value.
DotenvLoad()
int DotenvLoad(const char* filename);
Description: Loads environment variables from the specified file.
Returns: 0 on success, error code on failure.
DotenvGet()
const char* DotenvGet(const char* key, const char* defaultValue);
Description: Retrieves the value of an environment variable.
Returns: Pointer to the value string or default value.
DotenvHas()
int DotenvHas(const char* key);
Description: Checks if an environment variable exists.
Returns: 1 if exists, 0 otherwise.
DotenvClear()
void DotenvClear(void);
Description: Clears all loaded environment variables.
DotenvIsLoaded()
int DotenvIsLoaded(void);
Description: Checks if the library has loaded any file.
Returns: 1 if loaded, 0 otherwise.
DotenvGetLastError()
const char* DotenvGetLastError(void);
Description: Gets the last error message.
Returns: Pointer to error message string.
CallDotenvLoad() (Legacy)
void CallDotenvLoad(const char* filename_c_str);
Description: Legacy function for backwards compatibility. Use DotenvLoad instead.
C API Summary Table
| Function | Description |
|---|---|
DotenvLoad(filename) |
Load variables, returns 0 on success |
DotenvGet(key, default) |
Get variable value |
DotenvHas(key) |
Returns 1 if exists, 0 otherwise |
DotenvClear() |
Clear all loaded variables |
DotenvIsLoaded() |
Returns 1 if loaded, 0 otherwise |
DotenvGetLastError() |
Get last error message |
CallDotenvLoad(filename) |
Legacy function (backward compatible) |
Building from Source
Requirements
- C++17 compatible compiler (g++, clang++, MSVC)
- CMake 3.10+ or GNU Make
- Windows SDK (for code signing on Windows)
Build with CMake (Recommended)
# Create build directory
mkdir build && cd build
# Configure
cmake ..
# Build
cmake --build .
# Run tests
ctest --output-on-failure
Build with Make
Build the Library
# Build the library (auto-detects platform)
make
# Or explicitly
make lib
Run Tests
make test
Clean Build Artifacts
make clean
Install (Unix Systems)
sudo make install
# Or with custom prefix
sudo make install PREFIX=/usr/local
Compiler Flags
The library is built with the following flags:
-std=c++17- C++17 standard-O2- Optimization level 2-Wall -Wextra- All warnings enabled-DBUILDING_DOTENV_DLL- Export symbols for DLL-fPIC- Position independent code (Unix)-fvisibility=hidden- Hidden visibility by default (Unix)-static-libgcc -static-libstdc++- Static linking of runtime (Windows)
Output Files by Platform
| Platform | Library | Object File |
|---|---|---|
| Windows | bin/dotenv.dll, bin/libdotenv.a |
bin/dotenv.o |
| Linux | bin/libdotenv.so |
bin/dotenv.o |
| macOS | bin/libdotenv.dylib |
bin/dotenv.o |
Code Signing (Windows)
For production deployments, you can sign the DLL to verify its authenticity.
Create Test Certificate
For testing purposes, create a self-signed certificate:
powershell -ExecutionPolicy Bypass -File create-cert.ps1
Sign the DLL
make sign
This uses the default certificate (certificate.pfx) with password dotenv123.
Custom Certificate
To use a different certificate:
make sign CERT_FILE=path/to/cert.pfx CERT_PASSWORD=yourpassword
Production Certificates
For production use, purchase a code signing certificate from:
Examples
Web Server Configuration
#include "dotenv.hpp"
#include <iostream>
int main() {
// Load with error handling
DotenvError result = Dotenv::load(".env");
if (result != DotenvError::Success) {
std::cerr << "Failed to load .env: " << Dotenv::getLastError() << std::endl;
return 1;
}
// Get configuration with defaults
std::string host = Dotenv::get("SERVER_HOST", "localhost");
std::string port = Dotenv::get("SERVER_PORT", "8080");
std::cout << "Starting server on " << host << ":" << port << std::endl;
// Check debug mode
if (Dotenv::has("DEBUG") && Dotenv::get("DEBUG") == "true") {
std::cout << "Debug mode enabled" << std::endl;
}
return 0;
}
Database Connection
#include "dotenv.hpp"
#include <iostream>
class Database {
public:
static bool connect() {
if (Dotenv::load(".env") != DotenvError::Success) {
std::cerr << "Failed to load configuration" << std::endl;
return false;
}
// Check required variables
if (!Dotenv::has("DATABASE_URL") ||
!Dotenv::has("DB_USER") ||
!Dotenv::has("DB_PASSWORD")) {
std::cerr << "Missing database credentials" << std::endl;
return false;
}
std::string url = Dotenv::get("DATABASE_URL");
std::string user = Dotenv::get("DB_USER");
std::string pass = Dotenv::get("DB_PASSWORD");
std::cout << "Connecting to database..." << std::endl;
// Your database connection code here
return true;
}
};
int main() {
if (!Database::connect()) {
return 1;
}
return 0;
}
List All Loaded Variables
#include "dotenv.hpp"
#include <iostream>
int main() {
Dotenv::load(".env");
std::cout << "Loaded environment variables:" << std::endl;
for (const auto& key : Dotenv::getLoadedKeys()) {
std::cout << " " << key << "=" << Dotenv::get(key) << std::endl;
}
return 0;
}
C API with Runtime Loading
#include <stdio.h>
#include <dlfcn.h> // Use windows.h on Windows
typedef int (*DotenvLoadFunc)(const char*);
typedef const char* (*DotenvGetFunc)(const char*, const char*);
typedef int (*DotenvHasFunc)(const char*);
typedef void (*DotenvClearFunc)(void);
int main() {
void* lib = dlopen("./libdotenv.so", RTLD_LAZY);
if (!lib) {
printf("Failed to load library\n");
return 1;
}
// Get function pointers
DotenvLoadFunc load = dlsym(lib, "DotenvLoad");
DotenvGetFunc get = dlsym(lib, "DotenvGet");
DotenvHasFunc has = dlsym(lib, "DotenvHas");
DotenvClearFunc clear = dlsym(lib, "DotenvClear");
// Use the library
if (load(".env") == 0) {
printf("Database: %s\n", get("DATABASE_URL", "not set"));
if (has("API_KEY")) {
printf("API Key is configured\n");
}
}
// Cleanup
clear();
dlclose(lib);
return 0;
}
C# (.NET) P/Invoke Example
Use the library from .NET applications via P/Invoke:
using System;
using System.Runtime.InteropServices;
class DotenvExample
{
// Modern C API - Recommended
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DotenvLoad(string filename);
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr DotenvGet(string key, string defaultValue);
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DotenvHas(string key);
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void DotenvClear();
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int DotenvIsLoaded();
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr DotenvGetLastError();
// Legacy function (for backwards compatibility)
[DllImport("dotenv.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void CallDotenvLoad(string filename);
public static void Main()
{
// Load environment variables from .env file
int result = DotenvLoad(".env");
if (result != 0)
{
IntPtr errorPtr = DotenvGetLastError();
string error = errorPtr != IntPtr.Zero
? Marshal.PtrToStringAnsi(errorPtr)
: "Unknown error";
Console.WriteLine("Error loading .env: {0}", error);
return;
}
// Check if loaded successfully
if (DotenvIsLoaded() == 1)
{
Console.WriteLine("Environment variables loaded successfully!");
}
// Get environment variable with default value
IntPtr valuePtr = DotenvGet("CONNECTION_STRING", "default_connection");
string connectionString = Marshal.PtrToStringAnsi(valuePtr);
Console.WriteLine("Connection string: {0}", connectionString);
// Check if a variable exists
if (DotenvHas("API_KEY") == 1)
{
IntPtr apiKeyPtr = DotenvGet("API_KEY", "");
string apiKey = Marshal.PtrToStringAnsi(apiKeyPtr);
Console.WriteLine("API Key: {0}", apiKey);
}
// You can also use standard .NET methods after loading
string dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
Console.WriteLine("Database URL from Environment: {0}", dbUrl);
// Clear loaded variables when done
DotenvClear();
}
}
libdotenv.so instead of dotenv.dll.
On macOS, use libdotenv.dylib.
Troubleshooting
DLL/Shared Library Not Found
If you get an error that the library cannot be loaded:
- Windows: Ensure
dotenv.dllis in the same directory as your executable or in the system PATH - Linux: Use
LD_LIBRARY_PATHor install to a standard location - macOS: Use
DYLD_LIBRARY_PATHor use@rpath - Use an absolute path when calling
LoadLibrary()ordlopen()
Linking Errors
If you get linking errors:
- Make sure you're linking against the correct library for your platform
- Verify the library is in your linker's search path
- Check that
BUILDING_DOTENV_DLLis NOT defined when building your application - On Windows, link against
libdotenv.a(import library)
Environment Variables Not Loading
- Verify the .env file path is correct (use absolute paths if needed)
- Check file permissions
- Ensure the file format is correct (key=value pairs)
- Check
Dotenv::getLastError()for error messages - Verify the return value of
DotenvLoad()orDotenv::load()
Thread Safety Issues
- All library functions are thread-safe
- String pointers from C API are valid only until the next call to the same function
- Copy strings immediately if you need to preserve values across calls
Cross-Platform Build Issues
- Ensure you have a C++17 compatible compiler
- Use CMake for the most reliable cross-platform builds
- The Makefile auto-detects the platform (Windows/Linux/macOS)
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.