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.
Search across all documentation pages
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.
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.
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:
Prefer cobra for new work unless you are extending code already built on kingpin.
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:
Enum rejects values outside dry and apply at parse time.Duration flags parse Go duration syntax without custom Set methods.app.Command nodes; FullCommand() identifies which matched.kingpin.MustParse exits on grammar errors before run executes.Parse consumes os.Args[1:], applies defaults, runs type coercion, and returns the selected command string..Required(); optional positionals use .String() without Required.| 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 |
| 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"} |
cmd packages, not libraries.MustParse, business logic should live in plain functions for testability.os/exec.app vars make testing order-sensitive. Fix: wrap parse + action in functions accepting []string.os.Args. Fix: one framework per binary.| 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 |
Prefer cobra or urfave/cli for new code.
kingpin is appropriate when you extend an existing binary already written with it.
kingpin enums are declared at grammar build time and validate before actions run.
flag.Value validates per flag in the stdlib with more boilerplate.
Support is limited compared to cobra.
Plan manual completion scripts or migrate if tab completion is critical.
Call app.Parse([]string{"run", "--mode=dry"}) without touching os.Args, or test the run function after parse with injected values.
Validate in the Action after parse or migrate to cobra MarkFlagsMutuallyExclusive (Go 1.22+ pflag features).
Document conflicts clearly in help text.
Each Command gets its own help section generated from the fluent definitions.
Keep Help strings action-oriented ("execute shipment", not "run command").
Yes for Go CLIs; PowerShell users still expect long flags in scripts.
Document portable forms in README examples.
.ExistingFile() and .ExistingDir() stat paths during parse.
Fail fast before slow uploads when paths are wrong.
No.
Keep CLI grammar in main or cmd packages so library consumers are not forced into a CLI framework.
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.
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).
Reviewed by Chris St. John·Last updated Jul 19, 2026