Building CLIs with Cobra
Cobra is the library behind many famous Go command-line tools — kubectl, hugo, and the GitHub CLI. You'll build a root command, add subcommands, wire up flags (persistent and local) with pflag, validate arguments, and run it all with Execute() — plus a look at Viper for configuration.
Learn Building CLIs with Cobra in our free Go course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
1️⃣ The root command and Execute()
Every Cobra app has a root command : a &cobra.Command with a Use name, help text, and a Run function. In main you call rootCmd.Execute() , which parses the arguments and dispatches to the right command.
2️⃣ Subcommands and AddCommand
Real tools have subcommands like app version or app greet . Each is its own *cobra.Command attached with rootCmd.AddCommand . Use RunE to return errors and Args validators like cobra.ExactArgs(1) to check positional arguments.
3️⃣ Flags: persistent vs local (pflag)
Flags come from the spf13/pflag library (POSIX-style --long and -s short forms). A persistent flag on the root is inherited by every subcommand; a local flag belongs to one command only.
4️⃣ Configuration with Viper (a quick note)
Cobra's sibling spf13/viper handles configuration: it merges flags, environment variables, and config files into one set of values. Bind a flag with viper.BindPFlag and the same setting can come from --port , an env var, or a file.
🎯 Your Turn
Add a hello subcommand. Fill in the two blanks marked ___ to require one argument and attach the command.
❌ Forgetting rootCmd.AddCommand(child) — the subcommand never appears.
✅ Attach every subcommand to its parent (often in init() ).
❌ Never calling Execute() — the program does nothing.
✅ Call rootCmd.Execute() in main and check its error.
❌ Using Flags() for a global option — subcommands can't see it.
✅ Use PersistentFlags() on the root for inherited flags.
❌ Reading args[0] without validating — panics when no arg is given.
✅ Set Args: cobra.ExactArgs(1) (or MinimumNArgs ) so Cobra checks first.
A persistent flag via rootCmd.PersistentFlags() . Local flags from cmd.Flags() stay on one command.
rootCmd.Execute() . It parses args, finds the command, runs it, and returns an error to check.
Build a repeat subcommand with a --times flag that prints the given text N times, validating that at least one argument is provided.
Practice quiz
In Cobra, what does a single command map to?
- A &cobra.Command value with Use, Short, and a Run/RunE function
- A plain func main
- A JSON config file
- An HTTP route
Answer: A &cobra.Command value with Use, Short, and a Run/RunE function. Each command is a &cobra.Command; you set Use (its name and usage), Short/Long help, and a Run or RunE function.
How do you attach a subcommand to the root command?
- rootCmd.Add(cmd)
- rootCmd.Child(cmd)
- rootCmd.AddCommand(cmd)
- rootCmd.Sub(cmd)
Answer: rootCmd.AddCommand(cmd). rootCmd.AddCommand(childCmd) builds the command tree, e.g. app serve or app version.
What is the difference between Run and RunE?
- RunE runs faster
- RunE returns an error so Cobra can report it and set the exit code
- Run is only for the root command
- There is no difference
Answer: RunE returns an error so Cobra can report it and set the exit code. RunE has signature func(cmd, args) error; returning an error lets Cobra print it and exit non-zero, which is preferred.
Which function actually starts a Cobra program from main?
- rootCmd.Main()
- rootCmd.Run()
- rootCmd.Start()
- rootCmd.Execute()
Answer: rootCmd.Execute(). You call rootCmd.Execute() in main; it parses os.Args, finds the right command, and runs it.
A PERSISTENT flag defined on the root command is...
- available to the root command and all of its subcommands
- ignored unless re-declared
- the same as a positional argument
- available only on the root command
Answer: available to the root command and all of its subcommands. PersistentFlags are inherited by every child command; local Flags apply only to the command that defines them.
Which underlying library provides Cobra's flag parsing?
- viper
- spf13/pflag (POSIX-style flags)
- the standard flag package
- urfave/cli
Answer: spf13/pflag (POSIX-style flags). Cobra uses spf13/pflag, which supports POSIX double-dash long flags and single-dash shorthands.
How do you require exactly one positional argument?
- MinArgs: 1
- Args: "one"
- set Run to nil
- Args: cobra.ExactArgs(1)
Answer: Args: cobra.ExactArgs(1). Cobra ships validators like cobra.ExactArgs(n), cobra.MinimumNArgs(n), and cobra.NoArgs to check args before Run.
Tools like kubectl, hugo, and the GitHub CLI are well known for...
- avoiding any CLI framework
- being written in Python
- being built with Cobra's command/subcommand model
- using only environment variables
Answer: being built with Cobra's command/subcommand model. Cobra powers many large CLIs (kubectl, hugo, gh), which is why its subcommand structure feels familiar.
What is Viper commonly used for alongside Cobra?
- Rendering HTML
- Configuration: reading flags, env vars, and config files together
- Running tests
- Database migrations
Answer: Configuration: reading flags, env vars, and config files together. Viper is Cobra's sibling library for configuration, merging flags, environment variables, and files like YAML or JSON.
How do you define a local string flag named output with default "text"?
- cmd.Flags().StringVar / StringP for the flag
- cmd.Output("text")
- cmd.AddFlag("output")
- cmd.Set("output", "text")
Answer: cmd.Flags().StringVar / StringP for the flag. cmd.Flags().StringVar(&out, "output", "text", "...") (or StringP for a shorthand) declares a local string flag.