yodel

Yodel is a type-safe configuration loader for Gleam that handles JSON, YAML, and TOML configs with automatic format detection, environment variable resolution, profile-based configuration, and an intuitive dot-notation API.

Quick Start

import yodel

let assert Ok(ctx) = yodel.load("config.toml")
yodel.get_string(ctx, "database.host") // "localhost"

Key Features

Multiple Format Support

Load JSON, YAML, or TOML configuration files with automatic format detection:

yodel.load("config.yaml")  // Auto-detects YAML
yodel.load("config.toml")  // Auto-detects TOML
yodel.load("config.json")  // Auto-detects JSON

Environment Variable Resolution

Use placeholders to inject environment variables into your configuration:

export DATABASE_HOST="prod.db.example.com"
export API_KEY="secret-key-123"
# config.yaml
database:
  host: ${DATABASE_HOST}
  password: ${DB_PASSWORD:default-password}
api:
  key: ${API_KEY}

Placeholders support:

Profile-Based Configuration

Manage environment-specific configurations with profiles that automatically merge over your base configuration:

config/
├── config.yaml              # Base configuration (all environments)
├── config-dev.yaml          # Development overrides
├── config-staging.yaml      # Staging overrides
└── config-prod.yaml         # Production overrides

Activate profiles via environment variable:

export YODEL_PROFILES=dev,local
// Loads config.yaml, then config-dev.yaml, then config-local.yaml
// Later profiles override earlier ones
let assert Ok(ctx) = yodel.load("./config")

Or programmatically:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_profiles(["dev", "local"])
  |> yodel.load_with_options("./config")

Note: The YODEL_PROFILES environment variable takes precedence over programmatically set profiles, allowing runtime override without code changes. This makes it easy to change environments via deployment configuration.

Type-Safe Value Access

Access configuration values with type safety and helpful error messages:

let assert Ok(ctx) = yodel.load("config.yaml")

// Get values with type checking
let assert Ok(host) = yodel.get_string(ctx, "database.host")
let assert Ok(port) = yodel.get_int(ctx, "database.port")
let assert Ok(timeout) = yodel.get_float(ctx, "api.timeout")
let assert Ok(enabled) = yodel.get_bool(ctx, "features.new_ui")

// Provide defaults for optional values
let host = yodel.get_string_or(ctx, "cache.host", "localhost")
let port = yodel.get_int_or(ctx, "cache.port", 6379)

// Parse values from strings when needed
let assert Ok(port) = yodel.parse_int(ctx, "port")  // "8080" → 8080

Types

Errors that can occur when loading or parsing configuration.

pub type ConfigError =
  errors.ConfigError

The Context type, representing a loaded configuration.

This is an opaque type that holds your parsed configuration values. Create a context using load() or load_with_options(), then access values using the getter functions like get_string(), get_int(), etc.

let assert Ok(ctx) = yodel.load("config.yaml")
let value = yodel.get_string(ctx, "database.host")
pub type Context =
  @internal Context

Errors that occur when reading configuration files from disk.

pub type FileError =
  errors.FileError

The format of a configuration file.

Use the constants format_auto (default), format_json, format_toml, or format_yaml, or pass this type to with_format().

pub type Format =
  options.Format

Location of a syntax error in the source file.

pub type Location =
  errors.Location

Configuration options for loading config files.

Create using default_options() and configure using builder functions:

let ctx =
  yodel.default_options()
  |> yodel.as_yaml()
  |> yodel.with_resolve_strict()
  |> yodel.load_with_options("config.yaml")
pub type Options =
  options.Options

Errors that occur when parsing configuration content.

pub type ParseError =
  errors.ParseError

Errors that occur when accessing configuration values.

pub type PropertiesError =
  errors.PropertiesError

The resolve mode for environment variable placeholders.

Use the constants resolve_strict or resolve_lenient, or pass this type to with_resolve_mode().

  • resolve_strict: Fail if any placeholder cannot be resolved
  • resolve_lenient: Preserve unresolved placeholders as-is (default)
pub type ResolveMode =
  options.ResolveMode

Errors that occur when resolving environment variable placeholders.

pub type ResolverError =
  errors.ResolverError

Syntax errors with location information.

pub type SyntaxError =
  errors.SyntaxError

Type mismatch errors when accessing configuration values.

The got field contains the actual value that was found, which can be helpful for debugging configuration issues.

pub type TypeError =
  errors.TypeError

Errors that occur during configuration validation.

pub type ValidationError =
  errors.ValidationError

Represents a value stored in the configuration.

This type appears in TypeError when a type mismatch occurs, allowing you to see what value was actually present in the configuration.

pub type Value =
  value.Value

Values

pub fn as_auto(
  options options: options.Options,
) -> options.Options

Attempt to automatically detect the format of the configuration file.

If the input is a file, we first try to detect the format from the file extension. If that fails, we try to detect the format from the content of the file.

If the input is a string, we try to detect the format from the content.

If Auto Detection fails, an error will be returned because we can’t safely proceed. If this happens, try specifying the format using as_json, as_toml, as_yaml, or with_format.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.as_auto()
  |> yodel.load_with_options("config.yaml")
pub fn as_json(
  options options: options.Options,
) -> options.Options

Set the format of the configuration file to JSON.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.as_json()
  |> yodel.load_with_options(config_content)
pub fn as_toml(
  options options: options.Options,
) -> options.Options

Set the format of the configuration file to TOML.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.as_toml()
  |> yodel.load_with_options("config.toml")
pub fn as_yaml(
  options options: options.Options,
) -> options.Options

Set the format of the configuration file to YAML.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.as_yaml()
  |> yodel.load_with_options("config.json")
pub fn default_options() -> options.Options

The default options for loading a configuration file.

Default Options:

  • Format: format_auto
  • Resolve Enabled: True
  • Resolve Mode: resolve_lenient
  • Config Base Name: "config"
  • Profile Environment Variable: "YODEL_PROFILES"

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.load_with_options("config.toml")
pub fn describe_config_error(error: errors.ConfigError) -> String

Format a ConfigError into a human-readable string.

pub const format_auto: options.Format

Attempt to automatically detect the format of the configuration file.

If the input is a file, we first try to detect the format from the file extension. If that fails, we try to detect the format from the content of the file.

If the input is a string, we try to detect the format from the content.

If Auto Detection fails, an error will be returned because we can’t safely proceed. If this happens, try specifying the format using as_json, as_toml, as_yaml, or with_format.

This is the default.

pub const format_json: options.Format

Parse the configuration file as JSON.

pub const format_toml: options.Format

Parse the configuration file as TOML.

pub const format_yaml: options.Format

Parse the configuration file as YAML.

pub fn get_bool(
  ctx: @internal Context,
  key: String,
) -> Result(Bool, errors.PropertiesError)

Get a boolean value from the configuration. If the value is not a boolean, an error will be returned.

Example:

case yodel.get_bool(ctx, "foo") {
  Ok(value) -> value // True
  Error(e) -> Error(e)
}
pub fn get_bool_or(
  ctx: @internal Context,
  key: String,
  default: Bool,
) -> Bool

Get a boolean value from the configuration, or a default value if the key is not found.

Example:

let value = yodel.get_bool_or(ctx, "foo", False)
pub fn get_float(
  ctx: @internal Context,
  key: String,
) -> Result(Float, errors.PropertiesError)

Get a float value from the configuration. If the value is not a float, an error will be returned.

Example:

case yodel.get_float(ctx, "foo") {
  Ok(value) -> value // 42.0
  Error(e) -> Error(e)
}
pub fn get_float_or(
  ctx: @internal Context,
  key: String,
  default: Float,
) -> Float

Get a float value from the configuration, or a default value if the key is not found.

Example:

let value = yodel.get_float_or(ctx, "foo", 42.0)
pub fn get_int(
  ctx: @internal Context,
  key: String,
) -> Result(Int, errors.PropertiesError)

Get an integer value from the configuration. If the value is not an integer, an error will be returned.

Example:

case yodel.get_int(ctx, "foo") {
  Ok(value) -> value // 42
  Error(e) -> Error(e)
}
pub fn get_int_or(
  ctx: @internal Context,
  key: String,
  default: Int,
) -> Int

Get an integer value from the configuration, or a default value if the key is not found.

Example:

let value = yodel.get_int_or(ctx, "foo", 42)
pub fn get_string(
  ctx: @internal Context,
  key: String,
) -> Result(String, errors.PropertiesError)

Get a string value from the configuration. If the value is not a string, an error will be returned.

Example:

case yodel.get_string(ctx, "foo") {
  Ok(value) -> value // "bar"
  Error(e) -> Error(e)
}
pub fn get_string_or(
  ctx: @internal Context,
  key: String,
  default: String,
) -> String

Get a string value from the configuration, or a default value if the key is not found.

Example:

let value = yodel.get_string_or(ctx, "foo", "default")
pub fn load(
  from input: String,
) -> Result(@internal Context, errors.ConfigError)

Load a configuration file.

This function will read the config content, detect the format, resolve the placeholders, parse the config content, returning a Context if successful.

input can be a file path or a string containing the configuration content.

Example with file path:

let assert Ok(ctx) = yodel.load("config.toml")

Example with string content:

let yaml_content = "database:\n  host: localhost"
case yodel.load(yaml_content) {
  Ok(ctx) -> ctx
  Error(e) -> {
    // Handle error appropriately
    panic as yodel.describe_config_error(e)
  }
}
pub fn load_with_options(
  with options: options.Options,
  from input: String,
) -> Result(@internal Context, errors.ConfigError)

Load a configuration file with options.

This function will use the provided options to read and parse the config content, returning a Context if successful.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.as_yaml()
  |> yodel.with_resolve_strict()
  |> yodel.load_with_options("config.yaml")
pub fn parse_bool(
  ctx: @internal Context,
  key: String,
) -> Result(Bool, errors.PropertiesError)

Parse a bool value from the configuration.

If the value is not a bool, it will be converted to a bool. An error will be returned if the value is not a bool or cannot be converted to a bool.

Example:

case yodel.parse_bool(ctx, "foo") {
  Ok(value) -> value // True
  Error(e) -> Error(e)
}
pub fn parse_float(
  ctx: @internal Context,
  key: String,
) -> Result(Float, errors.PropertiesError)

Parse a float value from the configuration.

If the value is not a float, it will be converted to a float. An error will be returned if the value is not a float or cannot be converted to a float.

Example:

case yodel.parse_float(ctx, "foo") {
  Ok(value) -> value // 99.999
  Error(e) -> Error(e)
}
pub fn parse_int(
  ctx: @internal Context,
  key: String,
) -> Result(Int, errors.PropertiesError)

Parse an integer value from the configuration.

If the value is not an integer, it will be converted to an integer. An error will be returned if the value is not an integer or cannot be converted to an integer.

Example:

case yodel.parse_int(ctx, "foo") {
  Ok(value) -> value // 42
  Error(e) -> Error(e)
}
pub fn parse_string(
  ctx: @internal Context,
  key: String,
) -> Result(String, errors.PropertiesError)

Parse a string value from the configuration.

If the value is not a string, it will be converted to a string. An error will be returned if the value is not a string or cannot be converted to a string.

Example:

case yodel.parse_string(ctx, "foo") {
  Ok(value) -> value // "42"
  Error(e) -> Error(e)
}
pub fn resolve_disabled(
  options options: options.Options,
) -> options.Options

Disable placeholder resolution.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.resolve_disabled()
  |> yodel.load_with_options("config.yaml")
pub fn resolve_enabled(
  options options: options.Options,
) -> options.Options

Enable placeholder resolution.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.resolve_enabled()
  |> yodel.load_with_options("config.yaml")
pub const resolve_lenient: options.ResolveMode

Lenient Resolve Mode - Preserve unresolved placeholders.

This means ${foo} will remain as ${foo} if foo is not defined.

This is the default.

pub const resolve_strict: options.ResolveMode

Strict Resolve Mode - Fail if any placeholder is unresolved.

pub fn with_config_base_name(
  options options: options.Options,
  config_base_name config_base_name: String,
) -> options.Options

Set the base name for configuration files when loading from a directory.

The base name is used to identify configuration files matching the pattern {base_name}[-{profile}].{ext}. For example, with base name "settings":

  • settings.yaml → base config
  • settings-dev.yaml → dev profile
  • settings-prod.toml → prod profile

Default: "config"

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_config_base_name("settings")
  |> yodel.load_with_options("./config-dir")
// Looks for: settings.yaml, settings-dev.yaml, etc.
pub fn with_format(
  options options: options.Options,
  format format: options.Format,
) -> options.Options

Set the format of the configuration file.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_format(yodel.format_json)
  |> yodel.load_with_options("config.json")
pub fn with_profile_env_var(
  options options: options.Options,
  profile_env_var profile_env_var: String,
) -> options.Options

Set the environment variable name used to read active profiles.

By default, Yodel reads the YODEL_PROFILES environment variable. Use this to customize the environment variable name.

Default: "YODEL_PROFILES"

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_profile_env_var("APP_PROFILES")
  |> yodel.load_with_options("./config")
// Now reads from APP_PROFILES instead of YODEL_PROFILES
pub fn with_profiles(
  options options: options.Options,
  profiles profiles: List(String),
) -> options.Options

Set the active configuration profiles to load.

Profiles allow environment-specific configuration overrides. Profile configs are merged in the order specified, with later profiles overriding earlier ones.

Note: The YODEL_PROFILES environment variable takes precedence over programmatically set profiles.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_profiles(["dev", "local"])
  |> yodel.load_with_options("./config-dir")
// Loads: config.yaml, config-dev.yaml, config-local.yaml
// Values in config-local.yaml override config-dev.yaml
// Values in config-dev.yaml override config.yaml
pub fn with_resolve_enabled(
  options options: options.Options,
  resolve_enabled resolve_enabled: Bool,
) -> options.Options

Enable or disable placeholder resolution.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_resolve_enabled(False)
  |> yodel.load_with_options("config.yaml")
pub fn with_resolve_lenient(
  options options: options.Options,
) -> options.Options

Set the resolve mode to lenient.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_resolve_lenient()
  |> yodel.load_with_options(config_content)
pub fn with_resolve_mode(
  options options: options.Options,
  resolve_mode resolve_mode: options.ResolveMode,
) -> options.Options

Set the resolve mode.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_resolve_mode(yodel.resolve_strict)
  |> yodel.load_with_options("config.json")
pub fn with_resolve_strict(
  options options: options.Options,
) -> options.Options

Set the resolve mode to strict.

Example:

let assert Ok(ctx) =
  yodel.default_options()
  |> yodel.with_resolve_strict()
  |> yodel.load_with_options(config_content)
Search Document