zoobzio January 23, 2026 Edit this page

Concepts

This page covers the mental models for understanding fig: how configuration sources are prioritised, how struct tags declare intent, and how extension points work.

Resolution Order

fig resolves each field in a fixed order:

  1. Secret — query the secret provider (if configured)
  2. Environment variable — check os.Getenv
  3. Default — use the default tag value
  4. Zero value — leave the field uninitialised

This order is intentional. Secrets take precedence because they represent the most secure and specific source. Environment variables come next as the standard twelve-factor approach. Defaults provide sensible fallbacks.

type Config struct {
    // If secret "db/pass" exists, use it
    // Otherwise, check DB_PASSWORD env var
    // Otherwise, use "changeme"
    Password string `secret:"db/pass" env:"DB_PASSWORD" default:"changeme"`
}

Resolution stops at the first successful source. If a secret is found, the environment variable is never checked.

Struct Tags

fig uses four struct tags to declare configuration sources:

env

Maps a field to an environment variable:

Host string `env:"DATABASE_HOST"`

The value is read via os.Getenv. Empty environment variables are treated as unset—fig continues to the next source.

secret

Maps a field to a secret provider key:

Password string `secret:"database/credentials:password"`

The key format is provider-specific. See Secret Providers for conventions.

default

Provides a fallback value:

Port int `default:"5432"`

Defaults are parsed using the same type conversion as environment variables.

required

Marks a field as mandatory:

APIKey string `env:"API_KEY" required:"true"`

If no value is found from any source, Load returns a FieldError wrapping ErrRequired.

SecretProvider Interface

Secret providers implement a single method:

type SecretProvider interface {
    Get(ctx context.Context, key string) (string, error)
}

The interface is intentionally minimal. Providers handle their own authentication, caching, and retry logic. fig only cares about the final string value.

Error handling: Providers should return ErrSecretNotFound when a secret doesn't exist. Other errors propagate and fail the load. This distinction lets fig continue to environment variables when a secret is simply absent, while failing fast on actual errors (network issues, permission denied, etc.).

Built-in providers:

Validator Interface

Validation happens after all fields are populated:

type Validator interface {
    Validate() error
}

Implement this on your config struct for cross-field validation, range checks, or business logic:

func (c *Config) Validate() error {
    if c.MaxConns < c.MinConns {
        return errors.New("max connections must be >= min connections")
    }
    return nil
}

Validation errors are returned directly—they're not wrapped in FieldError because they may involve multiple fields.

Type Conversion

fig converts string values to the target type. Supported types include primitives (string, int, uint, float, bool), time.Duration, []string (comma-separated), and any type implementing encoding.TextUnmarshaler.

Conversion errors result in a FieldError wrapping ErrInvalidType. See Types Reference for the complete list with parsing details.

Nested Structs

fig recurses into nested structs automatically:

type DatabaseConfig struct {
    Host string `env:"DB_HOST"`
    Port int    `env:"DB_PORT"`
}

type Config struct {
    Database DatabaseConfig  // fig descends into this
}

The nested struct must not have any fig tags on the parent field. Tagged fields are treated as leaf values, not containers.

Next Steps