kingpin & Advanced CLI UX
kingpin (github.com/alecthomas/kingpin/v2) models CLIs as a fluent chain: application, nested commands, typed flags, positional arguments, and enums validated before your Action runs.
It influenced later frameworks and remains useful for reading legacy tools.
The upstream repository is archived; new greenfield CLIs should standardize on cobra or urfave/cli, but kingpin's UX patterns (typed parsing, required args, enum constraints) still apply everywhere.
Summary
kingpin separates parsing from execution: you declare the grammar first, then attach Action callbacks that receive fully validated values.
Enums restrict strings to a fixed set; counters and URLs parse into native types without manual strconv calls.
Advanced UX means failing fast with precise messages before any network or file I/O runs.
Recipe
Quick-reference recipe card - copy-paste ready.
app := kingpin.New("deploy", "deploy CLI")
env := app.Arg("env", "target environment").Required().Enum("staging", "prod")
timeout := app.Flag("timeout", "wait").Default("30s").Duration()
kingpin.MustParse(app.Parse(os.Args[1:]))
fmt.Println(*env, *timeout)When to reach for this:
- Maintaining an existing kingpin-based binary (legacy internal tools).
- Studying fluent CLI grammars with enum and type validation.
- Prototyping strict arg validation before committing to cobra.
Prefer cobra for new work unless you are extending code already built on kingpin.
Working Example
package main
import (
"fmt"
"os"
"time"
"github.com/alecthomas/kingpin/v2"
)
var (
app = kingpin.New("ship", "shipping tool")
verbose = app.Flag("verbose", "verbose output").Short('v').Bool()
mode = app.Flag("mode", "run mode").Enum("dry", "apply")
timeout = app.Flag("timeout", "operation timeout").Default("10s").Duration()
)
func main() {
cmd := kingpin.MustParse(app.Parse(os.Args[1:]))
switch cmd {
case shipRun.FullCommand():
run(*verbose, *mode, *timeout)
}
}
var shipRun = app.Command("run", "execute shipment")
func run(verbose bool, mode string, timeout time.Duration) {
fmt.Printf("verbose=%v mode=%s timeout=%s\n", verbose, mode, timeout)
}What this demonstrates:
Enumrejects values outsidedryandapplyat parse time.Durationflags parse Go duration syntax without customSetmethods.- Subcommands are
app.Commandnodes;FullCommand()identifies which matched. kingpin.MustParseexits on grammar errors beforerunexecutes.
Deep Dive
How It Works
- kingpin builds a parse tree at package init (typical pattern uses package-level vars).
Parseconsumesos.Args[1:], applies defaults, runs type coercion, and returns the selected command string.- Required args use
.Required(); optional positionals use.String()withoutRequired. - Help and version templates are customizable like other frameworks.
Validation Features at a Glance
| API | Purpose |
|---|---|
.Enum("a", "b") | Restrict string flag or arg |
.Required() | Fail parse if missing |
.Short('v') | Single-letter alias |
.Default("10s") | Pre-parse default for typed flags |
.ExistingFile() | Path must exist on disk |
.URL() | Parse and validate URL shape |
Migration to cobra
| kingpin | cobra equivalent |
|---|---|
app.Flag(...).Bool() | cmd.Flags().BoolVar |
app.Arg(...).Required() | Args: cobra.ExactArgs(1) + RunE |
.Enum(...) | custom validation in PreRunE or MarkFlagRequired + allowed set check |
app.Command("run", ...) | &cobra.Command{Use: "run"} |
Go Notes
- Package-level kingpin vars run at init; keep declarations in
cmdpackages, not libraries. - After
MustParse, business logic should live in plain functions for testability. - If migrating, port one subcommand at a time behind integration tests using
os/exec.
Gotchas
- Archived upstream - Security fixes may lag; plan migration for externally distributed tools. Fix: track cobra parity issues in a ticket and freeze new kingpin commands.
- Init-order coupling - Global
appvars make testing order-sensitive. Fix: wrap parse + action in functions accepting[]string. - Enum on unset flag - Required enums must have defaults or be required flags. Fix: document valid values in help strings.
- Mixing kingpin and stdlib flag - Two parsers fight over
os.Args. Fix: one framework per binary. - Over-nesting commands - Deep trees harm discoverability. Fix: flatten verbs or add topic-based groups in help.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| cobra | New tools, k8s ecosystem | You only maintain legacy kingpin |
| urfave/cli v2 | Hook-based app structure | You need kubebuilder scaffolds |
| kong | Struct tags drive CLI | Team knows kingpin already |
| stdlib flag | Two flags total | Enum validation is manual |
FAQs
Is kingpin safe for new projects?
Prefer cobra or urfave/cli for new code.
kingpin is appropriate when you extend an existing binary already written with it.
How do enums differ from custom flag.Value?
kingpin enums are declared at grammar build time and validate before actions run.
flag.Value validates per flag in the stdlib with more boilerplate.
Can kingpin generate shell completion?
Support is limited compared to cobra.
Plan manual completion scripts or migrate if tab completion is critical.
How do I test kingpin commands?
Call app.Parse([]string{"run", "--mode=dry"}) without touching os.Args, or test the run function after parse with injected values.
What about required mutually exclusive flags?
Validate in the Action after parse or migrate to cobra MarkFlagsMutuallyExclusive (Go 1.22+ pflag features).
Document conflicts clearly in help text.
How does kingpin handle subcommand help?
Each Command gets its own help section generated from the fluent definitions.
Keep Help strings action-oriented ("execute shipment", not "run command").
Are short flags portable to Windows?
Yes for Go CLIs; PowerShell users still expect long flags in scripts.
Document portable forms in README examples.
How do file validators work?
.ExistingFile() and .ExistingDir() stat paths during parse.
Fail fast before slow uploads when paths are wrong.
Should libraries import kingpin?
No.
Keep CLI grammar in main or cmd packages so library consumers are not forced into a CLI framework.
What UX patterns transfer to cobra?
Validate early, typed flags, explicit enums, and required args map directly to cobra PreRunE and flag validators.
Treat kingpin code as a spec when rewriting commands.
Related
- cobra & urfave/cli Command Trees - recommended modern tree
- flag Package & POSIX-Style Flags - stdlib baseline
- Color Output & User-Friendly Errors - parse error presentation
- CLI Tools Basics - manual validation patterns
- CLI Tools Best Practices - testing and migration hygiene
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).