urfave/cli: the build tag that gives up help template customization
A declarative, simple, fast, and fun package for building command line tools in Go
At a glance
- What is it?
- urfave/cli is a declarative Go package for command line tools with no dependencies outside the standard library, subcommand aliases, typed flags, and completion for four shells. The interesting part is what it trades away: a build tag shrinks the binary by dropping text/template and, in the same move, changes the help printer contract and makes one customization panic.
- Who is it for?
- urfave/cli fits a Go author who wants a command line interface in an afternoon, values typed flags and completion across shells, and does not want third party dependencies in the resulting binary.
- Can I use it commercially?
- Yes. MIT 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 November 14, 2026, and from our analysis. They are not legal advice.
Star history
- urfave/cli24,322 ★ · +80
| Repository | 2026-09-21 | 2026-11-14 |
|---|---|---|
| urfave/cli | 24.2k | 24.3k |
80 Stars in the last 54 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The no_template tag keeps the output and drops the customization
The build tag is the one place where this package makes a real trade, and the documentation is unusually honest about what is lost. Setting urfave_cli_no_template renders the default help and the fish completion without text/template, avoiding the text/template use that makes the Go linker disable dead code elimination. The output is the same. What changes is that help templates can no longer be customized: RootCommandHelpTemplate, CommandHelpTemplate, SubcommandHelpTemplate, and FishCompletionTemplate become constants. Custom template functions other than wrap and wrapAt are ignored. And if you use the default help printer together with a custom template such as CustomRootCommandHelpTemplate or CustomHelpTemplate, it panics rather than falling back. So the tag is a size decision with a runtime failure mode attached to it.
Replacing the help printer keeps working but receives a different argument
There is one escape from the rule above, and it has a catch worth reading closely. Swapping out cli.HelpPrinter continues to work as usual. The difference is what it receives: instead of the default template text, the templ argument arrives as the string root, command, or subcommand. A custom template passed in by the caller is still handed over as is, unchanged. Read as a contract, the tag means a library that accepts any help printer has to branch on whether templ is a template string or one of those three sentinels, and a printer that assumed it always received template text will now be handed a bare word. That is a source compatibility question for embedders, not only for the main package, and it is not something the feature list would tell you.
No dependencies past the standard library, apart from what the tests need
One of the advertised properties is that there are no dependencies except the Go standard library, and the module file mostly supports that claim for consumers. The module path is github.com/urfave/cli/v3 and it declares go 1.26. It does require github.com/stretchr/testify at v1.12.1, which is a test time dependency and does not reach a program that imports the library. It also carries go.yaml.in/yaml/v3 at v3.0.5 marked as indirect. Lookup from structured file formats is not in this module but in the separate urfave/cli-altsrc module, and documentation generation lives in urfave/cli-docs. Neither ships here, so the dependency-free claim holds for the import graph you inherit and stops at the edges of the module.
Three minor versions in three weeks behind a v3 import path
The release history moves faster than a major version number suggests. v3.12.0 is dated 2026-09-14 and v3.13.0 is dated 2026-09-20, with v3.14.0 dated 2026-10-02 as the newest tag, and the last push on the repository is dated 2026-11-02. That is three minor releases inside about three weeks on a single major line. Two things follow for anyone integrating it. The first is the import path: the module is github.com/urfave/cli/v3, so code written against v2 carries a different path and a search and replace rather than a version bump. The second is cadence: with minors this frequent, pinning a version in your own go.mod matters more than it would on a slower moving dependency, and it is worth deciding whether you track minors or sit on one.
One file per flag type, and a test that holds them to a schema
The root directory is a flat list of flag files, one per type, which tells you how the package is organised before you read a line of documentation. There is flag_bool.go and a separate flag_bool_with_inverse.go, flag_int.go beside flag_int_slice.go, flag_float.go beside flag_float_slice.go, flag_duration.go, flag_string.go, flag_string_map.go, flag_number_slice.go, flag_ext.go, flag_generic.go, and two shared bases in flag_slice_base.go and flag_map_impl.go. The advertised range of input types, simple types, slices of simple types, time, and duration, maps onto that list file for file. flag_mutex.go sits alongside them, and flag_schema_type_test.go suggests the types are covered by a test that checks them against a schema rather than by per type behaviour tests alone. For a contributor, adding a new flag type means adding a file here and satisfying that schema test.
Every make target is a pass-through to a Go program with global flags
The Makefile is a thin wrapper, on purpose. It defines GO_RUN_BUILD as go run scripts/build.go and then a catch-all rule that invokes that program with a global flag set and a command specific flag set, so anything defined in the Go script is reachable through make without a new target. The default target chains five steps: generate, vet, test, check-binary-size, and gfmrun. The example in the file's own comment passes a global flag through, using a packages option to narrow a test run. Two things follow. The binary size check in that chain is what keeps the no_template discussion honest, since a change that quietly pulls text/template back in shows up as a failing check. And the whole pipeline runs through a Go program, so a contributor debugging CI is reading Go, not make.
Documentation examples are executed by gfmrun, and the docs need mkdocs
The default target ends with gfmrun, which runs the code in Go flavoured Markdown, and the documentation site is built from the docs directory. That pairing explains why the repository also carries test files named after templates and examples, such as examples_template_test.go and fish_template_test.go, and why the example programs live in their own folders at examples/example-cli/ and examples/example-hello-world/. The effect is that a snippet in the docs is not a picture of an API, it is a program that has to compile and run. Two practical consequences. Editing a documentation example that no longer works fails the default target. And the docs toolchain is separate: the docs targets call mkdocs build and mkdocs serve, which is not a Go dependency in the module file, so building the site needs its own installation.
Editorial conclusion
urfave/cli fits a Go author who wants a command line interface in an afternoon, values typed flags and completion across shells, and does not want third party dependencies in the resulting binary. It does not fit a team that needs custom help templates and a small binary at the same time, since the no_template tag trades the first for the second, or a team that needs documentation and config files out of the box, because docs generation and structured config live in separate modules. Before adopting it, read the build tag section rather than the feature list, decide whether your help output is worth keeping customizable, and check the import path, since the module is versioned at v3.
Frequently asked questions
What is CLI and how is it used in urfave/cli?
It is what this Go package exists to build. urfave/cli gives a program commands and subcommands with alias and prefix match support, a help system, shell completion for bash, zsh, fish, and powershell, and typed flags covering simple types, slices of simple types, time, and duration.
Is CLI still used today?
The project is publishing on a current major version, with v3.12.0 dated 2026-09-14, v3.13.0 dated 2026-09-20, and v3.14.0 dated 2026-10-02 as the newest tag, and the last push is dated 2026-11-02. The project also asks readers to keep in mind that it is run by unpaid volunteers.
What are some of the best CLI apps in urfave/cli?
The repository does not rank applications. It ships two runnable examples of its own, at examples/example-cli/ and examples/example-hello-world/, which are the reference points for how a program is structured with this library.
golang urfave cli vs cobra
The repository makes no comparison with cobra. What it claims for itself is being declarative and having no dependencies except the Go standard library, and it names sibling projects it split features into, namely urfave/cli-docs and urfave/cli-altsrc.
Is CLI better than a GUI in urfave/cli?
The project takes no position on interfaces. It is a library for building command line tools in Go, and the closest it comes to a preference is a feature list covering subcommands, help, shell completion, and typed flags.
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/urfave-cli)Star history chart for your README
[](https://hysenlabs.com/projects/urfave-cli)Was this useful?