Golang Cobra Tutorial: Subcommands, Flags, and CLI Examples

Deepak Prasad
Tested on RHEL 10.2 with Go 1.26.5
Package go 1.26.5
github.com/spf13/cobra v1.10.2
Applies to Any host with Go 1.20+ installed
Privilege Normal user
Scope Hands-on golang cobra tutorial: root command, subcommands, local and persistent flags, --version, and terminal checks with go run. Does not cover Viper config wiring.
Related guides Golang flag examples
Golang prompt and interactive CLI
Golang Viper
Getting started with Go
Golang context

If you searched for golang cobra, go cobra tutorial, or a cobra golang example, you want a real command tree—not a single flag.Parse() call. Cobra is the library behind tools like Kubernetes, Hugo, and GitHub CLI: nested subcommands, POSIX-style flags, generated help, and shell completion. This walkthrough builds a small textcli program with reverse, uppercase, and modify subcommands, wires local and persistent flags, and verifies each path with go run.


What you will build

Subcommand Alias Arguments Result
reverse rev one string reversed runes
uppercase upper one string uppercased string
modify mod one string + optional -o appends a modified or MODIFIED suffix

A root-level -V / --verbose persistent flag is available on every subcommand; this tutorial wires it into reverse only to show inheritance.


Install Cobra and set up the module

Create the module and add Cobra (pin a release you have tested):

bash
go mod init example.com/textcli

go mod init writes go.mod in the current directory. Pull Cobra next:

bash
go get github.com/spf13/cobra@v1.10.2

Optional: install the project generator with go install github.com/spf13/cobra-cli@latest. This tutorial wires commands manually; cobra-cli init at the end shows the generated layout.

Lay out a minimal tree—main.go calls cmd.Execute(), and commands live in cmd/root.go:

text
textcli/
  cmd/root.go
  helper/helper.go
  main.go
  go.mod

Create the root command

Define the root cobra.Command and Execute in cmd/root.go:

go
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var version = "0.0.1"

var rootCmd = &cobra.Command{
	Use:     "textcli",
	Version: version,
	Short:   "String utilities CLI built with Cobra",
	Long:    "A small Go CLI that reverses, uppercases, and modifies strings using Cobra subcommands.",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Run a subcommand: reverse, uppercase, or modify")
	},
}

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

Use is the binary name in help. Version enables Cobra's built-in -v / --version on the root command.

Entry point in main.go:

go
package main

import "example.com/textcli/cmd"

func main() {
	cmd.Execute()
}
Output

Before subcommands are registered, root help shows only the built-ins:

bash
go run . --help
output
A small Go CLI that reverses, uppercases, and modifies strings using Cobra subcommands.

Usage:
  textcli [flags]
  textcli [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command

Flags:
  -h, --help      help for textcli
  -v, --version   version for textcli

Use "textcli [command] --help" for more information about a command.

Add subcommands and positional arguments

Add "example.com/textcli/helper" to the import block in cmd/root.go, then create helper/helper.go with the string helpers the commands call:

go
package helper

import "strings"

func Reverse(s string) string {
	rns := []rune(s)
	for i, j := 0, len(rns)-1; i < j; i, j = i+1, j-1 {
		rns[i], rns[j] = rns[j], rns[i]
	}
	return string(rns)
}

func Uppercase(s string) string {
	return strings.ToUpper(s)
}

func Modify(s string, opt bool) string {
	if opt {
		return s + "_" + "MODIFIED"
	}
	return s + "_" + "modified"
}

Cobra derives usage lines from each command's Use field—include the positional placeholder there. Declare option at package scope first (modifyCmd reads it; you bind the flag in the next section):

go
var option bool

var reverseCmd = &cobra.Command{
	Use:     "reverse [string]",
	Short:   "Reverse a string",
	Aliases: []string{"rev"},
	Args:    cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(helper.Reverse(args[0]))
	},
}

var uppercaseCmd = &cobra.Command{
	Use:     "uppercase [string]",
	Short:   "Uppercase a string",
	Aliases: []string{"upper"},
	Args:    cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(helper.Uppercase(args[0]))
	},
}

var modifyCmd = &cobra.Command{
	Use:     "modify [string]",
	Short:   "Modify a string",
	Aliases: []string{"mod"},
	Args:    cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println(helper.Modify(args[0], option))
	},
}

Args: cobra.ExactArgs(1) rejects wrong arity before Run runs. Aliases lets users type rev instead of reverse.

Register the subcommands once in init():

go
func init() {
	rootCmd.AddCommand(reverseCmd)
	rootCmd.AddCommand(uppercaseCmd)
	rootCmd.AddCommand(modifyCmd)
}

List commands again:

bash
go run . --help
output
Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  modify      Modify a string
  reverse     Reverse a string
  uppercase   Uppercase a string

Invoke each subcommand with arguments:

bash
go run . reverse hello
output
olleh

The rev alias behaves the same:

bash
go run . rev Cobra
output
arboC

Uppercase the argument:

bash
go run . upper golang
output
GOLANG

Local and persistent flags

Cobra has two flag scopes:

  • command.Flags() — local to that command
  • command.PersistentFlags() — inherited by child commands (shown under Global Flags in subcommand help)

Add a package-level verbose switch:

go
var verbose bool

Update only reverseCmd so -V prints a diagnostic before the result (other subcommands still inherit the flag in help, which is enough to show persistence):

go
var reverseCmd = &cobra.Command{
	Use:     "reverse [string]",
	Short:   "Reverse a string",
	Aliases: []string{"rev"},
	Args:    cobra.ExactArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		if verbose {
			fmt.Fprintln(os.Stderr, "verbose: reversing", args[0])
		}
		fmt.Println(helper.Reverse(args[0]))
	},
}

Open the existing init() and add these two lines at the top—keep your AddCommand calls as they are:

go
modifyCmd.Flags().BoolVarP(&option, "option", "o", false, "Append MODIFIED suffix when -o is set")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "V", false, "Print extra detail to stderr")

BoolVarP stores the parsed value before Run executes. PersistentFlags on rootCmd makes -V / --verbose appear under Global Flags on modify --help even though only reverse reads the variable here.

Default modify output (local -o flag omitted):

bash
go run . modify test
output
test_modified

With the local boolean flag:

bash
go run . modify test -o
output
test_MODIFIED

Persistent -V on reverse writes the diagnostic to stderr and the result to stdout:

bash
go run . reverse hello -V
output
stderr:
verbose: reversing hello

stdout:
olleh

modify help reflects Use, aliases, the local flag, and the inherited verbose flag:

bash
go run . modify --help
output
Modify a string

Usage:
  textcli modify [string] [flags]

Aliases:
  modify, mod

Flags:
  -h, --help     help for modify
  -o, --option   Append MODIFIED suffix when -o is set

Global Flags:
  -V, --verbose   Print extra detail to stderr

Built-in version flag and release builds

Because Version is set on rootCmd, Cobra exposes -v / --version:

bash
go run . --version
output
textcli version 0.0.1

For release binaries, inject a different version at link time:

bash
go build -o textcli -ldflags="-X 'example.com/textcli/cmd.version=0.0.2'" .

Confirm the injected value on the compiled binary:

bash
./textcli --version
output
textcli version 0.0.2

The -X path must match the package where version is declared. See the cmd/link -X documentation for the exact importpath.name=value syntax.


Scaffold with cobra-cli init

When you prefer generated boilerplate, run cobra-cli init inside a Go module:

bash
mkdir demoapp && cd demoapp
go mod init example.com/demoapp
cobra-cli init

Typical scaffold:

text
demoapp/
  cmd/root.go
  main.go
  LICENSE

cobra-cli add fetch creates additional command files under cmd/. Generated projects follow the same main.gocmd.Execute() pattern; customize Run functions and flags from there.


Troubleshooting

Symptom Likely cause Fix
unknown command for a subcommand Command not added in init() Call rootCmd.AddCommand(...) once per subcommand
Subcommand listed twice in help AddCommand called more than once Register each command in a single init()
accepts 1 arg(s), received 0 cobra.ExactArgs(1) with no positional Pass the string after the subcommand name
Flag works on parent but not on child Created with parent.Flags() Use parent.PersistentFlags() when descendants should inherit it
--version missing Version field empty on root Set Version: "0.0.1" on rootCmd
go run cannot find module Wrong directory or missing go.mod Run from module root; run go mod tidy after adding imports

Summary

This golang cobra tutorial walked through a complete go cobra example: install github.com/spf13/cobra, define a root command, register cobra subcommands with Use placeholders and ExactArgs, and bind both a local flag on modify and a persistent flag on the root that children inherit. You verified behavior by invoking the CLI with go run, arguments, and flags.

Cobra fits multi-command tools that outgrow the Golang flag package. Keep commands in cmd/, business logic in plain packages, and optionally scaffold with cobra-cli init. Pairing Cobra with Viper is the usual next step for config files and environment variables.


References


Frequently Asked Questions

1. What is Cobra in Golang?

Cobra is a library for building CLI apps with nested subcommands, POSIX-style flags, shell completion, and auto-generated help. Kubernetes, Hugo, and GitHub CLI use it.

2. How is golang cobra different from the flag package?

The standard flag package parses flat flags on one binary. Cobra adds a command tree (app server, app fetch), per-command flags, aliases, and richer help—better for multi-command tools.

3. How do I install Cobra and cobra-cli?

Add the library with go get github.com/spf13/cobra and install the generator with go install github.com/spf13/cobra-cli@latest. Run cobra-cli init inside a Go module to scaffold cmd/ files.

4. What is the difference between Flags() and PersistentFlags() in Cobra?

Flags() defines a local flag on one command. PersistentFlags() on a parent command registers a flag that child subcommands inherit and see under Global Flags in their help output.

5. How do I add a boolean flag to one subcommand?

Bind a variable with command.Flags().BoolVarP in init() after the command is defined. The flag applies only to that subcommand unless you use PersistentFlags on an ancestor.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms.

  • Go (programming language)
  • Python (programming language)
  • Java (programming language)
  • MongoDB
  • Kubernetes