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:
- Secret — query the secret provider (if configured)
- Environment variable — check
os.Getenv - Default — use the
defaulttag value - 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:
vault.Provider— HashiCorp Vaultawssm.Provider— AWS Secrets Managergcpsm.Provider— GCP Secret Manager
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
- Architecture — internal implementation details
- Secret Providers — implementing custom providers
- Validation — validation patterns
- Reference — complete type documentation