FireflyFirefly
DocsGuides
GitHub

Configuration

Firefly provides typed configuration from .env files and environment variables via Env.load.

.env Files

Create a .env file in your project root:

# .env
DATABASE_URL=postgres://localhost:5432/myapp
PORT=3000
JWT_SECRET=my-secret-key
DEBUG=true
API_BASE_URL=https://api.example.com

Supported formats:

# Comments
KEY=value
KEY="quoted value"
KEY='single quoted'
EMPTY=

Typed Configuration

Define an F# record and load it with Env.load:

type AppConfig = {
    DatabaseUrl: string
    Port: int
    JwtSecret: string
    Debug: bool
    ApiBaseUrl: System.Uri
}
 
let config = Env.load<AppConfig>()
// config.DatabaseUrl = "postgres://localhost:5432/myapp"
// config.Port = 3000
// config.Debug = true

Naming Convention

Record field names are converted to SCREAMING_SNAKE_CASE:

Field NameEnvironment Variable
DatabaseUrlDATABASE_URL
JwtSecretJWT_SECRET
ApiBaseUrlAPI_BASE_URL
PortPORT

Supported Types

F# TypeAccepted Values
stringAny string
intInteger (e.g., 3000)
floatNumber (e.g., 3.14)
booltrue, 1, yes / false, 0, no
System.UriValid URI string
System.TimeSpanTimeSpan string (e.g., 00:30:00)

Optional Fields

Use option types for fields that may not be present:

type AppConfig = {
    DatabaseUrl: string       // required — throws if missing
    RedisUrl: string option   // optional — None if missing
    Port: int                 // required
    Debug: bool option        // optional
}

Missing required fields produce a clear error listing all missing variable names:

Missing required environment variables: DATABASE_URL, PORT

Environment Priority

Real environment variables take precedence over .env file values. The .env file is only used to fill in variables that are not already set in the process environment. This is useful for local development without affecting deployed environments.

Usage with DI

Register your config as a singleton service:

type AppConfig = { DatabaseUrl: string; Port: int }
 
let appConfig = Env.load<AppConfig>()
 
let config =
    App.defaults
    |> App.port appConfig.Port
    |> App.services [
        Service.instance appConfig
    ]

Then access it in handlers via auto-DI (if it is an interface) or manually:

let handler (req: Request) = task {
    let config = req.Raw.RequestServices.GetRequiredService<AppConfig>()
    return Response.json {| db = config.DatabaseUrl |}
}

Per-Environment Configuration

Use the CLI template pattern with separate config modules:

// Config/Dev.fs
module MyApp.Config.Dev
 
let config =
    App.defaults
    |> App.port 3000
    |> App.middleware Cors.allowAll
 
// Config/Prod.fs
module MyApp.Config.Prod
 
let config =
    App.defaults
    |> App.port 8080
    |> App.middleware SecureHeaders.middleware
    |> App.middleware Compress.auto