go-flags: struct-tag driven command line parsing for Go, including subcommands
go command line option parser
At a glance
- What is it?
- The standard library gives you flags. This gives you short and long names, groups, namespaces, subcommands, generated man pages and shell completions, all declared with struct tags.
- Who is it for?
- go-flags earns its place in a codebase whose command line interface is part of the product rather than an afterthought. Reflection over struct tags gives you typed options, repeated flags, environment variable defaults and validated choices with far less code than a hand-rolled parser, and the subcommand support in command.go is the feature that genuinely has no equivalent in the standard library.
- Can I use it commercially?
- Yes. BSD-3-Clause 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 received new commits within the last day.
- 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 10, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A README that documents one example and points at the godoc
The first thing to know about this repository is that its README is thin. It opens with the project title, a godoc badge, and a feature list of seventeen bullets copied from the package documentation. Then it shows two code blocks, and the closing line is a pointer to godoc for everything else. There is no installation section, no changelog, no usage guidance beyond the single struct tag example.
That is unusual for a library with 2,694 stars and 329 forks under a BSD-3-Clause licence. Most projects of that size treat the README as the front door and treat godoc as the reference. This one inverts it. The practical consequence is that adopting the library means reading godoc, and the README is not a reliable summary of what the library can do.
There is no topics metadata on the repository either, which for a command line parsing library removes the most obvious way it would surface in a topic search. Combined with a name that describes a category rather than a product, discovery is the weak point of this project, not the code.
The last push was on 2026-10-04 and the repository is not archived, so the default branch is the live version of this library. That distinction matters more below.
Struct tags are the entire configuration language
The mechanism is reflection over struct field tags. A field's tag declares its short name, its long name, and a description that becomes help text:
type Options struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
}That one declaration produces both `-v` and `--verbose`, and because the field is a slice of bool, passing `-vvv` appends three true values rather than overwriting. Choosing the Go type is choosing the parsing behaviour: a bool field is a flag that takes no argument, an int or string field takes one, a slice appends, a map parses key and value pairs.
The longer example in the README walks through the rest of the vocabulary, and it is worth reading closely because the tags carry more meaning than they appear to. A field tagged `required:"true"` makes the option mandatory. A field with repeated `choice:"cat"` and `choice:"dog"` tags is validated against that set, and anything else is a parse error. `value-name:"FILE"` changes the placeholder shown in help output rather than the type. A pointer field means the option is optional and nil signals absence, which is how you distinguish "not passed" from "passed the zero value".
Two tags in that example are easy to miss. A `func(string)` field becomes a callback invoked each time the option appears, rather than a stored value. And an `env:"THRESHOLD_VALUES"` tag with `env-delim:","` seeds the field from an environment variable, split on the given delimiter, which lets configuration arrive from the environment without a flag. The repeated `default:"1"` and `default:"2"` on the same slice field seed multiple initial elements rather than one.
One small blemish: the example separates `env:"THRESHOLD_VALUES"` from `env-delim:","` with two spaces where every other tag pair in the file uses one. Struct tags treat runs of whitespace as separators, so it parses correctly, but it is the kind of formatting slip that survives in documentation because nothing fails.
The feature list is accurate, and it is the ceiling of what the README tells you
The seventeen bullets are the clearest statement of scope, so it is worth reading as a checklist rather than as marketing.
On the parsing side the library covers short and long names, options that do and do not take arguments, options with optional arguments and defaults, and multiple groups of options. It accepts the three argument spellings that trip up hand-rolled parsers: `-I/usr/include`, `-I=/usr/include` and `-I /usr/include`, as separate long options such as `-aux` where each letter is its own flag, and everything after a bare `--` passed through untouched. Unknown options can either error or be ignored. Primitive Go types are supported across string, the sized integer types and floats, and any option may be repeated to build a slice or to let the last occurrence win.
On the presentation side it generates formatted help, and the tree shows how much work that is. There are separate files for terminal width detection, with a Windows variant, a variant that avoids syscalls, and a defaults file, which is what lets help output wrap to the actual width of the terminal instead of a hardcoded column.
The word that does not appear anywhere in the feature list is subcommands. That is the omission worth dwelling on, because `command.go` and its test file are among the larger files in the tree and the capability is not something the standard library offers at all.
The feature the README never mentions: subcommands
If you arrive at go-flags for a single set of flags on a single binary, the standard library would have been enough. If you arrive for a command line interface with verbs, where each subcommand has its own options and its own help text, the reason to use this library is `command.go`.
A command can own its own options, can nest, and can be required. Each one gets help output scoped to itself rather than a flat wall of every option the binary accepts, which is the part that makes a growing CLI tractable. The namespace feature listed in the README bullets points at the same mechanism, since option groups can be nested into a hierarchy that maps onto commands.
None of this is described in the README. There is no code block for it, no mention of it in prose, and no hint that the seventeen-bullet list is not the whole surface. The `examples/` directory in the tree is where you would look for working code, and it is also not pointed at from the README.
The practical lesson is that the README cannot be used to evaluate this library. Read godoc, and read the files in the tree, because the tree is a more accurate index of capability than the documentation is. That is unusual enough to say plainly: it is a good library with an incomplete front door.
INI parsing, man pages, completions and suggestions
Several files in the tree describe capabilities that appear nowhere in the README, and each is significant enough that you should know they exist before choosing a different library.
`ini.go` is a full INI file parser integrated with the options struct, which turns this from a command line parser into a configuration system. A field can be populated from a config file section as well as from a flag, with flag values taking precedence. For a CLI tool that has grown more than a dozen options, that is often the difference between a usable tool and an unusable one, and it is the single strongest argument for this library over `flag` or `pflag`.
`man.go` generates a man page from the same struct that defines the options, so documentation stays in sync with the code by construction rather than by discipline. `completion.go` generates shell completion, which again falls out of the declaration instead of being maintained by hand.
`closest.go` is the one that tells you about the error handling philosophy. Its presence alongside `error.go` implies that unknown or misspelled options get a suggestion of the closest valid name, which is a small feature that changes how a user experiences a typo. If that is something you care about, it is worth confirming in godoc rather than assuming, because the README says nothing about it.
`multitag.go` exists because Go's reflect package handles a struct tag as a single string, and this library needs to treat repeated keys such as two `choice:` tags as separate entries. That one file is a reminder of what reflection-based configuration costs you in machinery.
Three releases on one afternoon, then silence
The release history needs reading carefully before you pin a version.
v1.5.0 was published on 2024-06-15 at 09:30 UTC. v1.6.0 followed the same day at 09:50. v1.6.1 landed at 12:25. So the most recent three tags all carry the same calendar date, roughly three hours apart, which is the signature of a maintainer cutting a series of releases in one sitting rather than shipping continuously.
The v1.6.0 notes are a useful summary of what changed in that series. The ini parser's write method was fixed for zero values, a panic was fixed when generating help while both the subcommand and every option group were hidden, and positional argument help was improved. That first item is a useful signal about `ini.go`: the config-file feature had a real bug in how it serialised zero values, which is exactly the kind of edge case you hit when a field is legitimately zero.
The v1.5.0 notes cover environment namespace handling, terminal size detection on WebAssembly, and Windows console width support. Those map directly onto the platform-specific files in the tree.
What has happened since 2024-06-15 is the part to note. The last push to the default branch is dated 2026-10-04, so there is more than two years of development that is not in any tagged release. The module declares Go 1.24, which is a current toolchain, so the code has been kept compatible with modern Go. With 55 open issues outstanding and no release in over two years, the sensible question is whether the default branch is production-ready. If you depend on this, find that out before you build on it, and if you need stability, pin v1.6.1 and expect to miss whatever came after.
Where it fits
The case for go-flags is a command line interface that has outgrown the standard library. Concretely, that means one or more of these: you need short and long option names together, you need subcommands with their own flags, you want typed options from struct tags instead of string lookups, you want values from environment variables or an INI file as well as flags, or you want help output and man pages generated from one declaration.
Where it is the wrong choice is equally clear. If you need a handful of boolean switches, `flag` is fine and a dependency is not worth it. If you need POSIX-style long options with GNU conventions specifically, `pflag` is the closer fit and has a larger ecosystem. If you need interactive prompts or TUI configuration, this library does not do that.
The honest summary is that this is a capable library with a documentation gap. The code supports more than the README admits, the godoc is where the real reference lives, and the release cadence has been quiet for over two years while the main branch kept moving. That combination makes it a reasonable dependency for a small to medium CLI, and a dependency you should pin deliberately.
Editorial conclusion
go-flags earns its place in a codebase whose command line interface is part of the product rather than an afterthought. Reflection over struct tags gives you typed options, repeated flags, environment variable defaults and validated choices with far less code than a hand-rolled parser, and the subcommand support in command.go is the feature that genuinely has no equivalent in the standard library. The cost is a dependency and a learning curve for the tag vocabulary. Before you commit to it, read the godoc rather than the README, because the README documents one example and defers everything else, including the subcommand system that most adopters arrive for. And check the release situation: the last tagged version is from June 2024 while the default branch has moved on, so decide early whether you will track the branch or a tag.
Frequently asked questions
How is go-flags different from Go's built-in flag package?
The built-in package accepts flags as separate strings and gives you strings back, so you write your own parsing and validation. go-flags reflects over a struct with field tags, so options arrive as typed values, repeated flags build slices, choices are validated against a declared set, and required options are enforced by the tag rather than by hand. It also adds subcommands, option groups, environment variable defaults and INI file configuration, none of which the standard library provides.
Does go-flags support subcommands and nested command groups?
Yes, through command.go and the namespace mechanism, and each command can carry its own option set with help output scoped to that command. This is the feature with no equivalent in the built-in flag package, and it is the reason many projects choose go-flags. Be aware that the README does not document it at all, so read the godoc and the examples directory instead.
Can go-flags read configuration from a file or the environment?
Yes. The ini.go file provides INI file parsing wired into the same options struct, so fields can be populated from a config file section with command line flags taking precedence. Individual fields can also carry an env tag with a delimiter, which seeds the value from an environment variable and splits it on the given separator. Neither capability appears in the README, which is worth knowing before you evaluate alternatives.
What is the latest version of go-flags and is main branch safe to use?
The most recent tagged release is v1.6.1, published on 2024-06-15, and v1.5.0 and v1.6.0 went out the same day. The default branch has been pushed to as recently as 2026-10-04 and the module declares Go 1.24, so there is more than two years of work that is not in any tag. With 55 open issues and no recent release, pin v1.6.1 if you need stability and confirm the main branch separately if you want the newer code.
How do I let a flag be specified multiple times, like -vvv?
Declare the field as a slice, so a bool field typed as a slice of bool appends a true value each time the option is encountered. Passing -vvv then yields a slice of three true values rather than one overwritten value. The same slice handling gives you repeated string options, and a map field parses repeated key and value pairs into a Go map.
Does go-flags generate help text and man pages automatically?
Help output is generated from the struct tags, including the description text, value names and terminal width, with separate files handling Windows and non-Windows terminal size detection. A man page generator and a shell completion generator exist in the tree as well. Nothing about any of this is documented in the README, so the godoc is the only complete reference the project offers.
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/jessevdk-go-flags)