Cobra Basics

Updated

September 8, 2026

Cobra Basics

Overview

Cobra is the most common Go library for rich CLIs (kubectl, hugo, gh patterns). It gives you command trees, POSIX flags (via pflag), help generation, and hooks for completion.

Use Cobra when stdlib FlagSet dispatch becomes repetitive—not for every 50-line script.

Install

mkdir mytool && cd mytool
go mod init example.com/mytool
go get github.com/spf13/cobra@latest
# optional generator:
go install github.com/spf13/cobra-cli@latest

Minimal root + subcommand

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

func main() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

var rootCmd = &cobra.Command{
    Use:   "greet",
    Short: "A friendly greeter",
    Long:  "greet prints a greeting with optional flair.",
    // Run when user types bare `greet` (optional)
    RunE: func(cmd *cobra.Command, args []string) error {
        return cmd.Help()
    },
}

var helloCmd = &cobra.Command{
    Use:   "hello [name]",
    Short: "Say hello",
    Args:  cobra.MaximumNArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        name := "world"
        if len(args) == 1 {
            name = args[0]
        }
        times, _ := cmd.Flags().GetInt("times")
        for i := 0; i < times; i++ {
            fmt.Printf("hello, %s\n", name)
        }
        return nil
    },
}

func init() {
    helloCmd.Flags().IntP("times", "n", 1, "repeat count")
    rootCmd.AddCommand(helloCmd)
}
go run . hello Ada -n 2
go run . hello --help

Run vs RunE

Field Behavior
Run func(cmd, args) — errors must be handled inside
RunE returns error — Cobra prints and sets exit code

Prefer RunE for testable, consistent errors.

Args validators

Args: cobra.ExactArgs(1),
Args: cobra.MinimumNArgs(1),
Args: cobra.NoArgs,
Args: cobra.OnlyValidArgs, // with ValidArgs

Custom:

Args: func(cmd *cobra.Command, args []string) error {
    if len(args) < 1 {
        return fmt.Errorf("requires a key")
    }
    return nil
},

Local flags

// only on this command
helloCmd.Flags().String("lang", "en", "language")

Project layout with Cobra

cmd/
  root.go       // rootCmd, Execute()
  hello.go      // helloCmd + init AddCommand
  version.go
main.go         // cmd.Execute()
// main.go
package main

import "example.com/mytool/cmd"

func main() {
    cmd.Execute()
}
// cmd/root.go
package cmd

import (
    "os"
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{Use: "mytool", Short: "demo"}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

Example: kv with Cobra

var getCmd = &cobra.Command{
    Use:   "get KEY",
    Short: "Get a value",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        raw, _ := cmd.Flags().GetBool("raw")
        val, ok := store[args[0]]
        if !ok {
            return fmt.Errorf("not found")
        }
        if raw {
            fmt.Print(val)
            return nil
        }
        fmt.Printf("%s=%s\n", args[0], val)
        return nil
    },
}

func init() {
    getCmd.Flags().Bool("raw", false, "raw value only")
    rootCmd.AddCommand(getCmd)
}

Silencing usage on errors

rootCmd.SilenceUsage = true  // don't dump full help on every runtime error
rootCmd.SilenceErrors = true // handle printing yourself

Useful once commands stabilize—usage on every “not found” is noisy.

Mapping to stdlib skills

Stdlib Cobra
switch os.Args[1] AddCommand tree
flag.FlagSet cmd.Flags()
flag.Args() args in RunE
manual help text auto --help

Rules of thumb

Do Don’t
Keep domain out of cmd package Put SQL in RunE closures forever
Use RunE Ignore errors in Run
One file per command group as you grow 2k-line root.go

Try next

  1. Port the stdlib greet tool to Cobra with -n.
  2. Add version subcommand.
  3. Set SilenceUsage and compare error UX.