peterbourgon/ff: flags-first Go configuration parsing
Flags-first package for configuration
At a glance
- What is it?
- The ff library gives Go programs one configuration model built around flags, with parsers for command-line arguments, environment variables, and config files. It suits command-line tools that want a visible configuration surface, although its README describes v4 as pre-release.
- Who is it for?
- ff is appropriate for Go command-line tools whose settings should remain discoverable through flags, especially applications modeled on tools such as kubectl or docker. It is a weaker fit for programs whose primary configuration model is a structured document that flags only mirror, or for teams that require a stable v4 contract.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why ff treats configuration as a flag surface
ff solves a specific consistency problem in Go command-line programs: configuration can arrive from several places, but it remains difficult for users to discover what a program accepts when each source has a separate definition. Its flags-first approach makes every configuration parameter a flag. The stated goal is that `myprogram -h` presents the complete configuration surface area.
That policy is particularly relevant to command-line applications with user-visible runtime controls. A service listening on an address, enabling debug logging, or changing a refresh interval can all remain attached to flags even when the value later comes from an environment variable or file. Users gain one vocabulary for discovery and invocation, while the program gains one parsing entry point.
The package targets Go developers rather than end users directly. Based on the repository layout and README examples, its natural users build command-line programs and want declarative configuration handling without designing precedence, environment-name mapping, file selection, and repeated flag types independently. ff does not itself define an application schema or expose a network service. It supplies the parsing and flag-definition layer that application code calls.
Three configuration sources and one precedence order
The primary parsing mechanism is `ff.Parse`. A program supplies a flag set, command-line arguments, and options that enable environment-variable and config-file processing. The README documents this priority order:
1. Command-line arguments have the highest priority. 2. Environment variables have the next-highest priority. 3. Config files have the lowest priority.
The reasoning is concrete. An explicit command-line value represents the user's immediate choice, an environment variable represents the runtime session, and a config file represents more static host configuration. This is not an arbitrary source list. It defines what happens when the same setting appears in more than one place, which is one of the harder parts of adding multi-source configuration to a small Go program.
The standard-library route starts with `flag.NewFlagSet`, defines normal Go flags, and passes the set to `ff.Parse` instead of calling `flag.FlagSet.Parse`. The richer route uses `ff.NewFlagSet`, which supports short and long names, repeatable string sets, enums, and other typed helpers. In both cases, option functions connect environment prefixes, the config-file flag, and a parser. The resulting flag values remain variables in the Go program, which print or consume them after parsing.
Installing ff and creating the first parse
The README does not provide a `go get` installation command. It directs readers to the `github.com/peterbourgon/ff/v4` package reference and explicitly says that the described version is pre-release, while the stable line is linked as `ff/v3`. The repository's `go.mod` identifies v4 as requiring Go 1.21.0. Version selection is therefore the first installation issue rather than a command that can be inferred from the README.
For a first v4 program, define each setting as a flag, then parse arguments with environment and file options. The following is the README's basic example. It registers `listen`, `refresh`, `debug`, and `config`, with `MY_PROGRAM` as the environment prefix and `ff.PlainParser` as the file parser.
fs := flag.NewFlagSet("myprogram", flag.ContinueOnError)
var (
listenAddr = fs.String("listen", "localhost:8080", "listen address")
refresh = fs.Duration("refresh", 15*time.Second, "refresh interval")
debug = fs.Bool("debug", false, "log debug information")
_ = fs.String("config", "", "config file (optional)")
)The declarations establish defaults and help descriptions before parsing. The application then passes these flags and its arguments to ff.
A complete first-use Go example
The parser reads command-line arguments first, then environment variables, then the selected plain configuration file. The file itself is requested through the `config` flag.
ff.Parse(fs, os.Args[1:],
ff.WithEnvVarPrefix("MY_PROGRAM"),
ff.WithConfigFileFlag("config"),
ff.WithConfigFileParser(ff.PlainParser),
)
fmt.Printf("listen=%s refresh=%s debug=%v\n", *listenAddr, *refresh, *debug)With the README's example values, `myprogram -listen=localhost:9090` changes the listen address. Setting `MY_PROGRAM_DEBUG=1` enables debug logging through the environment. A plain file can set duration and boolean values:
printf 'refresh 30s \n debug \n' > my.conf
myprogram -config=my.confThe README shows the resulting values as `listen=localhost:8080 refresh=30s debug=true`. That output illustrates the documented precedence contract and the expected parsing result for the sample program.
Typed flags, parent commands, and explicit help handling
`ff.FlagSet` is the getopts-inspired alternative to adapting `flag.FlagSet`. It can bind short and long names to the same setting, accepts repeatable strings through `StringSet`, and restricts selected values through `StringEnum`. Its parser also permits compact combinations in the README example: `-afoo`, `-a bar`, `--addr=baz`, `-ct`, and an environment override for the log level.
Parent flag sets support command hierarchies without copying shared options. A child created with `SetParent` parses its own flags plus those defined by the parent. In the README example, the child owns `--compress`, `--transform`, and `--refresh`, while the parent owns `--log` and `--config`. This is useful when a tool has common global options and command-specific options.
Help handling is deliberately different from `flag.FlagSet`. With `ff.FlagSet`, a failed parse does not invisibly write help text to `os.Stderr`. The caller must inspect the returned error and decide whether to print help. The companion `ffhelp` package provides standard and custom help formatting. That design gives applications control over output streams, but it also means applications must implement their own error branch rather than relying on automatic stderr output.
Parser boundaries and the v4 release trade-off
The package supports parsers for multiple file formats through repository entries such as `ffjson`, `ffyaml`, and `fftoml`, while the basic README example uses `ff.PlainParser`. Those package names show format-specific integration points, but the README does not describe every parser's options or behavior. Applications should verify that the selected parser matches the file format and expected values before adopting the library.
The clearest limitation is version stability. The README says it describes pre-release v4, while its stable-version link points to `ff/v3`. The module path is also versioned as `/v4`. A team that values a stable public API may therefore need to choose v3 or explicitly accept v4's pre-release status. The recent release entries shown for v3 do not change that warning, and the repository's last push was on 2026-04-23. The repository is not archived, but its API stability still requires a deliberate decision.
ff is also the wrong tool when configuration is primarily a rich structured document that users do not need to express as command-line flags. Requiring every parameter to appear in the flag surface can make a CLI-centric design less natural for a service configured primarily through files. The README presents this as a deliberate design, not a universal configuration standard.
License, maintenance, and adoption cost
The repository is licensed under Apache-2.0. The module requires Go 1.21.0, so the immediate adoption constraint is the application's toolchain and build environment. The repository is not archived, and its last push was on 2026-04-23. The README still warns that v4 is pre-release, so recent repository activity does not remove the versioning question.
The upgrade cost depends on which API style is chosen. Programs already using `flag.FlagSet` can keep standard flag declarations and change the parse call plus add options. Programs adopting `ff.FlagSet` may rewrite declarations to use short and long names and typed helpers. Child commands add another layer through `SetParent`. These are localized changes for a small CLI, but they are still API and error-handling decisions rather than a zero-configuration drop-in.
The README does not document a migration procedure or rollback process. That absence matters for production adoption, especially when deciding between v3 and v4. Engineers should inspect the package API for their chosen version before committing to a parser or upgrade path.
Editorial conclusion
ff is appropriate for Go command-line tools whose settings should remain discoverable through flags, especially applications modeled on tools such as kubectl or docker. It is a weaker fit for programs whose primary configuration model is a structured document that flags only mirror, or for teams that require a stable v4 contract. Verify the version decision first, then confirm that the required parser, typed flag methods, help behavior, and Go version match the application.
Frequently asked questions
How do I install peterbourgon/ff?
The README does not include a `go get` command. It points to the `github.com/peterbourgon/ff/v4` package reference, notes that the described v4 is pre-release, and links to v3 as the stable version. The repository's `go.mod` specifies Go 1.21.0 for v4.
How does ff combine command-line arguments, environment variables, and config files?
`ff.Parse` accepts a flag set, arguments, and options for environment and file handling. The documented precedence is command-line arguments first, environment variables second, and config files last.
Does ff automatically print help when parsing fails?
The README says `ff.FlagSet` does not emit help to `os.Stderr` as an invisible side effect. Callers must check the returned parse error, and the `ffhelp` package provides standard or custom help formatting.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/peterbourgon-ff)Was this useful?