Claude
Skills
Sign in
Back

elixir-config

Included with Lifetime
$97 forever

Guide for Elixir application configuration. Use when configuring runtime vs compile-time settings, managing config.exs/runtime.exs, or using Application.get_env.

General

What this skill does


# Elixir Configuration

Guide for proper application configuration in Elixir, with emphasis on understanding and correctly using runtime vs compile-time configuration.

## When to Activate

Use this skill when:
- Setting up or modifying application configuration
- Choosing between `config.exs` and `runtime.exs`
- Deciding between `Application.compile_env` and `Application.get_env`
- Debugging configuration-related issues
- Working with releases or deployment configuration
- Migrating from `use Mix.Config` to `import Config`
- Writing libraries that need configuration

## Critical Principle

> **Runtime configuration is the preferred approach.** Only use compile-time configuration when values must affect compilation itself.

## Configuration Files

### config/config.exs (Compile-Time)

Evaluated during project compilation, before your application starts.

```elixir
import Config

# Basic configuration
config :my_app, MyApp.Repo,
  database: "my_app_dev",
  username: "postgres",
  password: "postgres",
  hostname: "localhost"

# Environment-specific config
config :my_app,
  environment: config_env()

# Import environment-specific config files
import_config "#{config_env()}.exs"
```

**Key characteristics:**
- Runs at compile time
- Uses `import Config` (not `use Mix.Config`)
- Can use `config_env()` and `config_target()`
- Can import other config files with `import_config/1`
- Deep-merges keyword lists
- **Library config.exs is NOT evaluated when used as a dependency**

### config/runtime.exs (Runtime)

Evaluated right before applications start in both Mix and releases.

```elixir
import Config

# Read from environment variables
config :my_app, MyApp.Repo,
  database: System.get_env("DATABASE_NAME") || "my_app_dev",
  username: System.get_env("DATABASE_USER") || "postgres",
  password: System.get_env("DATABASE_PASSWORD") || "postgres",
  hostname: System.get_env("DATABASE_HOST") || "localhost",
  pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")

# Conditional runtime configuration
if config_env() == :prod do
  config :my_app, MyAppWeb.Endpoint,
    secret_key_base: System.fetch_env!("SECRET_KEY_BASE"),
    http: [port: String.to_integer(System.fetch_env!("PORT"))]
end
```

**Key characteristics:**
- Runs at application startup (both dev and prod)
- Executes in both Mix projects and releases
- Perfect for environment variables and runtime values
- **Does NOT support `import_config/1`**
- Can use `System.get_env` and `System.fetch_env!`

### config/dev.exs, config/test.exs, config/prod.exs

Environment-specific compile-time configuration, typically imported from `config.exs`:

```elixir
# config/config.exs
import_config "#{config_env()}.exs"

# config/dev.exs
import Config

config :my_app, MyApp.Repo,
  show_sensitive_data_on_connection_error: true,
  pool_size: 10

# config/test.exs
import Config

config :my_app, MyApp.Repo,
  pool: Ecto.Adapters.SQL.Sandbox,
  pool_size: 10

# config/prod.exs
import Config

# Production-specific compile-time config only
config :my_app, MyAppWeb.Endpoint,
  cache_static_manifest: "priv/static/cache_manifest.json"
```

## Accessing Configuration

### Runtime Access (Preferred)

Use in function bodies to read configuration at runtime:

#### Application.get_env/3

```elixir
defmodule MyApp.Service do
  def start_link do
    # Get with default value
    timeout = Application.get_env(:my_app, :timeout, 5000)
    GenServer.start_link(__MODULE__, timeout, name: __MODULE__)
  end
end
```

**When to use:**
- Reading config in function bodies (most common)
- When a sensible default exists
- When config might change between environments

#### Application.fetch_env!/2

```elixir
defmodule MyApp.Mailer do
  def deliver(email) do
    # Raise if not configured (for required config)
    api_key = Application.fetch_env!(:my_app, :mailgun_api_key)
    send_email(email, api_key)
  end
end
```

**When to use:**
- Required configuration that must exist
- When you want explicit errors for missing config
- When no sensible default exists

#### Application.fetch_env/2

```elixir
defmodule MyApp.Cache do
  def get(key) do
    case Application.fetch_env(:my_app, :cache_adapter) do
      {:ok, adapter} -> adapter.get(key)
      :error -> nil  # No caching configured
    end
  end
end
```

**When to use:**
- Optional configuration
- When you need pattern matching on result
- When absence of config is a valid state

### Compile-Time Access (Use Sparingly)

Use only when configuration must affect compilation:

#### Application.compile_env/3

```elixir
defmodule MyApp.JSONEncoder do
  # Only use compile_env when the value affects compilation
  @json_library Application.compile_env(:my_app, :json_library, Jason)

  def encode(data) do
    # The specific library is compiled into the module
    @json_library.encode(data)
  end
end
```

**When to use:**
- Configuration affects which code gets compiled
- Performance-critical paths where indirection is costly
- Compile-time optimizations or code generation

**Warning:** Mix tracks compile-time config and raises errors if values diverge between compile and runtime.

#### Application.compile_env!/2

```elixir
defmodule MyApp.Adapter do
  # Raises at compile time if not configured
  @adapter Application.compile_env!(:my_app, :storage_adapter)

  def store(data) do
    @adapter.put(data)
  end
end
```

**When to use:**
- Required compile-time configuration
- Adapters or behaviors selected at compile time

## Common Patterns

### Pattern 1: Environment Variables in Runtime

**Correct approach:**

```elixir
# config/runtime.exs
import Config

config :my_app,
  api_url: System.get_env("API_URL") || "http://localhost:4000",
  api_key: System.fetch_env!("API_KEY")  # Required in production
```

**Access in code:**

```elixir
defmodule MyApp.Client do
  def call(endpoint) do
    api_url = Application.fetch_env!(:my_app, :api_url)
    api_key = Application.fetch_env!(:my_app, :api_key)
    HTTPoison.get("#{api_url}/#{endpoint}", [{"Authorization", api_key}])
  end
end
```

### Pattern 2: Development vs Production Config

**config/config.exs:**

```elixir
import Config

# Shared configuration for all environments
config :my_app, :shared_setting, "value"

# Import environment-specific config
import_config "#{config_env()}.exs"
```

**config/dev.exs:**

```elixir
import Config

config :my_app, MyApp.Repo,
  database: "my_app_dev",
  show_sensitive_data_on_connection_error: true
```

**config/runtime.exs:**

```elixir
import Config

# Runtime config for all environments
if config_env() == :prod do
  # Production-specific runtime config
  database_url = System.fetch_env!("DATABASE_URL")

  config :my_app, MyApp.Repo,
    url: database_url,
    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
end
```

### Pattern 3: Storing config_env() for Runtime Access

**Problem:** Can't call `config_env()` at runtime.

**Solution:** Store it in config:

```elixir
# config/config.exs
import Config

config :my_app, :environment, config_env()

# Then in your code:
defmodule MyApp do
  def environment do
    Application.fetch_env!(:my_app, :environment)
  end

  def development? do
    environment() == :dev
  end
end
```

### Pattern 4: Optional Features Based on Config

```elixir
defmodule MyApp.Telemetry do
  def setup do
    case Application.fetch_env(:my_app, :telemetry_backend) do
      {:ok, :datadog} -> setup_datadog()
      {:ok, :prometheus} -> setup_prometheus()
      :error -> :ok  # Telemetry disabled
    end
  end
end
```

### Pattern 5: Child Spec with Runtime Config

```elixir
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      MyApp.Repo,
      {MyApp.Worker, Application.fetch_env!(:my_app, :worker_opts)},
      MyAppWeb.Endpoint
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

## Anti-Patterns to Avoid

### ❌ Using compile_env for Runtime Values

```elixir
# DON'

Related in General