A type-safe .env file parser for Swift.
- Type-safe access to environment variables with generics
- Built-in support for
String,Int,Double,Float,Bool, andURL - Custom type support via
DotEnvRepresentableprotocol - Configuration structs via
DotEnvConfigurableprotocol - Quoted values (single and double quotes) with escape sequence support
- Merge multiple
.envfiles with override precedence - Load values into process environment
- Swift 6 concurrency support (
Sendable) CodableandExpressibleByDictionaryLiteralconformance
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/kylebrowning/dotenv", from: "1.0.0")
]Then add dotenv to your target dependencies:
.target(
name: "YourTarget",
dependencies: ["dotenv"]
)import dotenv
// Load from file
let env = try DotEnv(path: ".env")
// Type-safe access
let port: Int = try env.require("PORT")
let debug: Bool = env.get("DEBUG") ?? false
let timeout: Int = env.get("TIMEOUT", default: 30)
// Check if key exists
if env.has("API_KEY") {
let key: String = try env.require("API_KEY")
}
// Raw string access
let value = env["SOME_KEY"]Create type-safe configuration by conforming to DotEnvConfigurable:
struct AppConfig: DotEnvConfigurable {
let databaseURL: String
let port: Int
let debug: Bool
let apiKey: String
init(from env: DotEnv) throws {
self.databaseURL = try env.require("DATABASE_URL")
self.port = try env.require("PORT")
self.debug = env.get("DEBUG") ?? false
self.apiKey = try env.require("API_KEY")
}
}
// Load from file
let config = try AppConfig.load(from: ".env")
// Load from multiple files (later files override earlier)
let config = try AppConfig.load(from: [".env", ".env.local"])
// Load from string
let config = try AppConfig.load(contents: "PORT=8080\nDEBUG=true")Conform your types to DotEnvRepresentable for type-safe parsing:
enum Environment: String, DotEnvRepresentable {
case development
case staging
case production
}
let env = try DotEnv(path: ".env")
let appEnv: Environment? = env.get("APP_ENV")Enums with String or Int raw values automatically get DotEnvRepresentable conformance.
// Merge multiple files (later files take precedence)
let env = try DotEnv.merged(from: ".env", ".env.local", ".env.development")
// Or merge manually
let base = try DotEnv(path: ".env")
let local = try DotEnv(path: ".env.local")
let merged = base.merging(with: local)
// Load with common overrides pattern (.env + .env.local)
let env = try DotEnv.loadWithOverrides()
// Auto-detect from common locations
let env = try DotEnv.loadDefault() // Searches: .env.local, .env, .env.developmentLoad values into the process environment:
let env = try DotEnv(path: ".env")
// Load without overwriting existing vars
env.loadIntoProcessEnvironment()
// Load and overwrite existing vars
env.loadIntoProcessEnvironment(overwrite: true)
// Values now accessible via ProcessInfo
let value = ProcessInfo.processInfo.environment["MY_KEY"]// From string contents
let env = DotEnv(contents: "KEY=value\nPORT=8080")
// From dictionary
let env = DotEnv(values: ["KEY": "value", "PORT": "8080"])
// Dictionary literal
let env: DotEnv = ["KEY": "value", "PORT": "8080"]
// Empty
let env = DotEnv()# Comments start with #
API_KEY=your_api_key_here
# Quoted values preserve spaces
MESSAGE="Hello, World!"
SINGLE='No escape processing'
# Double quotes support escape sequences
MULTILINE="Line 1\nLine 2"
WITH_TAB="Col1\tCol2"
# Values can contain equals signs
CONNECTION=host=localhost;port=5432
# Whitespace around = is trimmed
KEY = valueThe following values are recognized as booleans (case-insensitive):
| True | False |
|---|---|
true |
false |
yes |
no |
1 |
0 |
on |
off |
do {
let env = try DotEnv(path: ".env")
let port: Int = try env.require("PORT")
} catch let error as DotEnvError {
switch error {
case .fileNotFound(let path):
print("File not found: \(path)")
case .missingKey(let key):
print("Missing required key: \(key)")
case .invalidValue(let key, let value, let type):
print("Cannot convert '\(value)' to \(type) for '\(key)'")
case .readError(let path, let underlying):
print("Failed to read '\(path)': \(underlying)")
}
}MIT