Type-safe configuration code generation for Go.
Define your config in TOML, generate strongly-typed Go code with zero runtime dependencies.
# config.toml
[server]
addr = ":8080"
timeout = "30s"
$ cfgx generate --in config.toml --out config/config.go
// Generated
var Server = ServerConfig{
Addr: ":8080",
Timeout: 30 * time.Second,
}
v0.x.x — The API may introduce breaking changes between minor versions. That said, cfgx
is a small, focused tool that's production-ready for baking configuration at build time. We encourage you to use it in production systems.
Traditional config loading in Go involves:
- Writing structs by hand
- Loading files at runtime
- Unmarshaling with error handling
- Runtime parsing overhead
cfgx
generates everything at build time. No runtime parsing, no reflection, no config files to deploy. Just plain Go code with your values baked in.
vs. viper/koanf:
- Zero runtime overhead
- Compile-time type safety
- Self-contained binaries
- No reflection
Trade-off: Config is baked at build time. For multi-environment setups, generate from different TOML files per environment during your build process.
go install github.com/gomantics/cfgx/cmd/cfgx@latest
cfgx generate --in config.toml --out config/config.go
//go:generate cfgx generate --in config.toml --out config/config.go
Then run:
go generate ./...
cfgx generate --in server.toml --out config/server.go
cfgx generate --in worker.toml --out config/worker.go
cfgx generate [flags]
Flags:
-i, --in string Input TOML file (default "config.toml")
-o, --out string Output Go file (required)
-p, --pkg string Package name (inferred from output path)
--no-env Disable environment variable overrides
--max-file-size Maximum file size for file: references (default "1MB")
Supports: KB, MB, GB (e.g., "5MB", "1GB", "512KB")
Automatic type inference with smart duration detection:
[server]
port = 8080 # int64
host = "localhost" # string
debug = true # bool
timeout = "30s" # time.Duration (auto-detected)
[database]
max_conns = 25
retry_delay = "5s"
Generates:
type ServerConfig struct {
Port int64
Host string
Debug bool
Timeout time.Duration
}
var Server = ServerConfig{
Port: 8080,
Host: "localhost",
Debug: true,
Timeout: 30 * time.Second,
}
[database.primary]
dsn = "postgres://localhost/app"
[database.replica]
dsn = "postgres://replica/app"
Generates nested structs automatically.
[app]
hosts = ["api.example.com", "web.example.com"]
ports = [8080, 8081]
intervals = ["30s", "1m", "5m"]
Override any value at generation time:
export CONFIG_SERVER_ADDR=":3000"
export CONFIG_DATABASE_MAX_CONNS="50"
cfgx generate --in config.toml --out config/config.go
The generated code will contain the overridden values. Useful for CI/CD pipelines where you inject secrets at build time.
Use --no-env
to disable this feature.
Embed file contents directly into your generated code using the file:
prefix:
[server]
addr = ":8080"
tls_cert = "file:certs/server.crt"
tls_key = "file:certs/server.key"
[app]
logo = "file:assets/logo.png"
sql_schema = "file:migrations/schema.sql"
Generates:
type ServerConfig struct {
Addr string
TlsCert []byte // Embedded certificate bytes
TlsKey []byte // Embedded key bytes
}
var Server = ServerConfig{
Addr: ":8080",
TlsCert: []byte{
0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43,
// ... actual cert bytes ...
},
TlsKey: []byte{ /* ... key bytes ... */ },
}
Key features:
- Paths are relative to the TOML file location
- Files are read at generation time - no runtime I/O
- Self-contained binaries - no need to distribute separate files
- Size limits - defaults to 1MB, configurable via
--max-file-size
- Any file type - text, json, binary, certificates, images, etc.
Example usage:
cfgx generate --in config.toml --out config/config.go --max-file-size 5MB
Use cases:
- TLS certificates and keys
- SQL migration schemas
- Template files
- Small assets (logos, icons)
- Configuration snippets
- Test fixtures
- Primitives:
string
,int64
,float64
,bool
- Duration:
time.Duration
(auto-detected from Go duration strings:"30s"
,"5m"
,"2h30m"
) - File content:
[]byte
(use"file:path/to/file"
prefix) - Arrays: Arrays of any supported type
- Nested tables: Becomes nested structs
- Array of tables:
[]StructType
# Development
cfgx generate --in config.dev.toml --out config/config.go
# Production
cfgx generate --in config.prod.toml --out config/config.go
FROM golang:1.25.1 as builder
WORKDIR /app
COPY . .
RUN go install github.com/gomantics/cfgx/cmd/cfgx@latest
RUN cfgx generate --in config.toml --out config/config.go
RUN go build -o app
Set environment variables in your CI system:
CONFIG_DATABASE_DSN="postgres://prod.example.com/db"
CONFIG_SERVER_ADDR=":443"
cfgx generate --in config.prod.toml --out config/config.go && go build -o app-prod
cfgx generate --in config.dev.toml --out config/config.go && go build -o app-dev
Yes. Like sqlc
and protoc
, commit the generated code. It's part of your source tree and should be versioned.
However: Don't commit TOML files with production secrets. Keep those in your secrets manager and inject via environment variables during build.
No. The whole point is that config is baked into your binary. No runtime file loading needed.
No. The generated file includes a // Code generated ... DO NOT EDIT
marker. Regenerate instead.
TOML is designed for config: readable, has comments, unambiguous types, no indentation issues. YAML and JSON don't handle config as well.
- Secret manager integration (AWS Secrets Manager, Google Secret Manager)
- Config validation rules
- Default value annotations
- sqlc — If sqlc can generate type-safe database code, why not config?
- protoc — Schema-first development works
Issues and PRs welcome. This is a small, focused tool — let's keep it that way.
For feature requests, start a discussion here.