home / skills / andrueandersoncs / claude-skill-effect-ts / configuration

configuration skill

/skills/configuration

This skill helps you manage effect configuration safely by loading, validating, and composing environment variables and JSON config.

npx playbooks add skill andrueandersoncs/claude-skill-effect-ts --skill configuration

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.4 KB
---
name: Configuration
description: This skill should be used when the user asks about "Effect Config", "environment variables", "configuration management", "Config.string", "Config.number", "ConfigProvider", "Config.nested", "Config.withDefault", "Config.redacted", "sensitive values", "config validation", "loading config from JSON", "config schema", or needs to understand how Effect handles application configuration.
version: 1.0.0
---

# Configuration in Effect

## Overview

Effect provides type-safe configuration loading with:

- Automatic environment variable reading
- Validation and type conversion
- Default values and composition
- Sensitive value handling
- Multiple config sources (env, JSON, custom)

## Basic Configuration Types

```typescript
import { Config, Effect } from "effect"

const host = Config.string("HOST")

const port = Config.number("PORT")

const debug = Config.boolean("DEBUG")

const maxConnections = Config.integer("MAX_CONNECTIONS")
```

## Using Config in Effects

```typescript
const program = Effect.gen(function* () {
  const host = yield* Config.string("DATABASE_HOST")
  const port = yield* Config.number("DATABASE_PORT")

  return { host, port }
})

// Runs and reads from environment
await Effect.runPromise(program)
```

## Default Values

```typescript
const port = Config.number("PORT").pipe(
  Config.withDefault(3000)
)

const debug = Config.boolean("DEBUG").pipe(
  Config.withDefault(false)
)
```

## Optional Configuration

```typescript
const apiKey = Config.string("API_KEY").pipe(
  Config.option
)
// Type: Effect<Option<string>>
```

## Combining Configurations

### Using Config.all

```typescript
const dbConfig = Config.all({
  host: Config.string("DB_HOST"),
  port: Config.number("DB_PORT"),
  database: Config.string("DB_NAME"),
  maxConnections: Config.number("DB_MAX_CONN").pipe(
    Config.withDefault(10)
  )
})

const program = Effect.gen(function* () {
  const config = yield* dbConfig
  // config: { host: string, port: number, database: string, maxConnections: number }
})
```

### Nested Configurations

```typescript
const dbConfig = Config.nested("DB")(
  Config.all({
    host: Config.string("HOST"),      // Reads DB_HOST
    port: Config.number("PORT"),      // Reads DB_PORT
    name: Config.string("NAME")       // Reads DB_NAME
  })
)
```

## Config with Schema Validation

Use Effect Schema for complex validation:

```typescript
import { Config, Schema } from "effect"

const PortSchema = Schema.Number.pipe(
  Schema.int(),
  Schema.between(1, 65535)
)

const port = Config.number("PORT").pipe(
  Config.mapOrFail((n) =>
    Schema.decodeUnknownEither(PortSchema)(n).pipe(
      Either.mapLeft((e) => ConfigError.InvalidData([], `Invalid port: ${n}`))
    )
  )
)
```

## Handling Sensitive Values

### Config.redacted

Prevents accidental logging of sensitive values:

```typescript
const apiKey = Config.redacted("API_KEY")
// Type: Effect<Redacted<string>>

const program = Effect.gen(function* () {
  const key = yield* apiKey

  // Safe to log - shows "<redacted>"
  yield* Effect.log(`Key: ${key}`)

  // Get actual value when needed
  const actual = Redacted.value(key)
})
```

### Secret Type

```typescript
const dbPassword = Config.secret("DB_PASSWORD")
// Type: Effect<Secret.Secret>

const program = Effect.gen(function* () {
  const password = yield* dbPassword
  const value = Secret.value(password) // Get actual string
})
```

## Config Operators

### Transforming Values

```typescript
const upperHost = Config.string("HOST").pipe(
  Config.map((s) => s.toUpperCase())
)

const port = Config.string("PORT").pipe(
  Config.mapOrFail((s) => {
    const n = parseInt(s)
    return isNaN(n)
      ? Either.left(ConfigError.InvalidData([], "Not a number"))
      : Either.right(n)
  })
)
```

### Fallback Values

```typescript
const host = Config.string("PRIMARY_HOST").pipe(
  Config.orElse(() => Config.string("SECONDARY_HOST")),
  Config.orElse(() => Config.succeed("localhost"))
)
```

## Custom Config Providers

### From Environment (Default)

```typescript
const program = Effect.gen(function* () {
  const host = yield* Config.string("HOST")
})
```

### From JSON/Object

```typescript
import { ConfigProvider, Layer } from "effect"

const config = {
  host: "localhost",
  port: "3000",
  database: {
    host: "db.example.com",
    port: "5432"
  }
}

const JsonConfigProvider = ConfigProvider.fromJson(config)

const program = Effect.gen(function* () {
  const host = yield* Config.string("host")
  const dbHost = yield* Config.nested("database")(Config.string("host"))
})

const runnable = program.pipe(
  Effect.provide(Layer.setConfigProvider(JsonConfigProvider))
)
```

### From Map

```typescript
const MapProvider = ConfigProvider.fromMap(
  new Map([
    ["HOST", "localhost"],
    ["PORT", "3000"]
  ])
)
```

### Combining Providers

```typescript
const CombinedProvider = ConfigProvider.orElse(
  ConfigProvider.fromEnv(),
  () => ConfigProvider.fromJson(defaultConfig)
)
```

## Config in Layers

```typescript
const AppConfigLive = Layer.effect(
  AppConfig,
  Effect.gen(function* () {
    const host = yield* Config.string("HOST")
    const port = yield* Config.number("PORT")
    const debug = yield* Config.boolean("DEBUG").pipe(Config.withDefault(false))

    return { host, port, debug }
  })
)
```

## Testing Configuration

### Mock Config Provider

```typescript
const TestConfigProvider = ConfigProvider.fromMap(
  new Map([
    ["HOST", "test-host"],
    ["PORT", "9999"]
  ])
)

const testProgram = program.pipe(
  Effect.provide(Layer.setConfigProvider(TestConfigProvider))
)
```

### Config.succeed for Hardcoded

```typescript
const testConfig = Config.succeed({
  host: "localhost",
  port: 3000
})
```

## Error Handling

Config failures produce `ConfigError`:

```typescript
const program = Effect.gen(function* () {
  const host = yield* Config.string("REQUIRED_HOST")
}).pipe(
  Effect.catchTag("ConfigError", (error) =>
    Effect.fail(new StartupError({ cause: error }))
  )
)
```

## Complete Example

```typescript
import { Config, Effect, Layer, Schema } from "effect"

// Define config shape
const AppConfig = Config.all({
  server: Config.nested("SERVER")(
    Config.all({
      host: Config.string("HOST").pipe(Config.withDefault("0.0.0.0")),
      port: Config.number("PORT").pipe(Config.withDefault(3000))
    })
  ),
  database: Config.nested("DATABASE")(
    Config.all({
      url: Config.redacted("URL"),
      maxConnections: Config.number("MAX_CONN").pipe(Config.withDefault(10))
    })
  ),
  features: Config.all({
    debug: Config.boolean("DEBUG").pipe(Config.withDefault(false)),
    metrics: Config.boolean("METRICS_ENABLED").pipe(Config.withDefault(true))
  })
})

// Use in application
const program = Effect.gen(function* () {
  const config = yield* AppConfig
  yield* Effect.log(`Starting server on ${config.server.host}:${config.server.port}`)
})
```

## Best Practices

1. **Use Config.withDefault for optional values** - Avoid runtime errors
2. **Use Config.redacted for secrets** - Prevents accidental logging
3. **Use Config.nested for structure** - Organizes related config
4. **Validate with Schema** - Catch invalid config early
5. **Test with mock providers** - Deterministic tests

## Additional Resources

For comprehensive configuration documentation, consult `${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt`.

Search for these sections:
- "Configuration" for full API reference
- "ConfigProvider" for custom providers
- "Handling Sensitive Values" for security

Overview

This skill explains how to use Effect's configuration system to load, validate, and manage application settings. It covers environment and JSON providers, type-safe config primitives, nested structures, defaults, and handling sensitive values. You'll get practical patterns for composing configs and integrating them into Effects and Layers.

How this skill works

Effect exposes Config primitives (Config.string, Config.number, Config.boolean, Config.secret, Config.redacted, etc.) that read values from configurable providers like the environment, JSON objects, or Maps. Config combinators (Config.all, Config.nested, Config.withDefault, Config.option, Config.mapOrFail) let you build typed configurations, validate values with Schema, and transform or provide fallbacks. Providers are pluggable so you can swap environment input for JSON or test maps and supply a provider via Layers.

When to use it

  • When you need type-safe reading of environment variables or JSON-based config.
  • When configuration values require validation or conversion (e.g., port number ranges).
  • When secrets must be protected from accidental logs (use redacted/secret).
  • When grouping related settings into nested structures for clarity.
  • When writing tests and you need deterministic config via mock providers.

Best practices

  • Apply Config.withDefault for optional or safe fallback values to avoid runtime failures.
  • Use Config.redacted or Config.secret for any sensitive values to prevent accidental logging.
  • Group related keys with Config.nested and Config.all to keep a clear config shape.
  • Validate complex values with Effect Schema and mapOrFail to surface errors at startup.
  • Provide test-specific providers (ConfigProvider.fromMap/fromJson) for deterministic tests.

Example use cases

  • Loading a database block with Config.nested('DB') and defaults for max connections.
  • Reading numeric ports with Schema validation to ensure 1–65535 and fail fast on bad input.
  • Swapping the default environment provider for a JSON provider in integration tests.
  • Combining primary and fallback keys using Config.orElse to support multiple deploy patterns.
  • Protecting API keys with Config.redacted so logs show <redacted> while code can access the real value when needed.

FAQ

How do I supply config values from a JSON object instead of environment variables?

Create a provider with ConfigProvider.fromJson(yourObject) and provide it via Layer.setConfigProvider before running your program.

How can I ensure secrets are not logged?

Use Config.redacted or Config.secret for sensitive fields. Redacted values print safely while allowing retrieval of the real value in controlled code paths.

What happens if a required config value is missing?

Config failures produce a ConfigError. Use Config.withDefault or catch ConfigError at startup to convert it into a clearer startup error.