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.