Cobra
A powerful library for creating modern CLI applications in Go. Inspired by tools like git and kubectl.
GitHub Overview
spf13/cobra
A Commander for modern Go CLI interactions
Topics
Star History
Framework
Cobra
Overview
Cobra is a library for creating powerful modern command-line interfaces (CLI) in Go. It's used by many well-known projects including Kubernetes, Hugo, and GitHub CLI. It provides features such as subcommand-based CLIs, POSIX-compliant flags, and automatic help generation, making it easy to build complex CLI applications like Git. The latest version is v1.8.1 (updated June 2024), establishing its position as the most trusted CLI building library in the Go ecosystem.
Details
Cobra is built around three core concepts: Commands
, Args
, and Flags
. Applications follow the structure APPNAME COMMAND ARG --FLAG
, providing an intuitive and consistent interface. Developed by spf13 (Steve Francia), who is also known as the Product Lead of the Go programming language at Google.
Dominant Position in Go Ecosystem
Why Cobra is the most popular Go CLI library:
- Industry Standard Adoption: Used by major projects like Kubernetes, Hugo, GitHub CLI, Docker CLI
- Complete Feature Set: Supports everything from simple flag parsing to complex subcommand structures
- Excellent Developer Experience: Auto-completion, help generation, and error messages come standard
- Mature Design: API refined through years of development and practical use
- Framework-Library Balance: Provides rich functionality while maintaining control
Key Features
- Subcommand-based CLIs: Hierarchical command structure like
app server
orapp fetch
- POSIX-compliant Flags: Support for both short and long forms (
-n
and--name
) - Nested Subcommands: Create more organized and intuitive CLI structures
- Automatic Help Generation: Automatically generates help for all commands and flags
- Intelligent Suggestions: Suggests correct commands for typos (using Levenshtein distance)
- Shell Autocompletion: Auto-generates completion scripts for Bash, Zsh, Fish, and PowerShell
- Command Aliases: Change command names without breaking existing workflows
- Flag Groups: Define relationships between flags (required together, mutually exclusive, etc.)
Advanced Features
- Custom Completion: Dynamic completion via
ValidArgsFunction
andRegisterFlagCompletionFunc
- Active Help: Hint messages to guide users during shell completion
- Lifecycle Hooks: Hook functions like
PreRun
,PostRun
,PersistentPreRun
- Viper Integration: Easy integration with the Viper configuration management library (now independent)
Latest Changes (2024-2025)
- Lightweight Dependencies: Removed Viper and its indirect dependencies from the core library
- Cobra-cli Separation: Code generation tool moved to independent repository (spf13/cobra-cli)
- Release Strategy Change: Moved from seasonal releases to generic point release targets
- Enhanced Active Help: Improved inline warnings and hints during Tab completion
- Continuous Completion Improvements: Ongoing enhancements to shell completion capabilities
Pros and Cons
Pros
- Ideal for large, complex CLI applications
- Rich features and customization options
- Proven track record in famous projects (Kubernetes, Hugo, GitHub CLI, etc.)
- Excellent documentation and community support
- Superior UX with auto-completion and help generation
- Pluggable architecture
- Automatic code generation and scaffolding via cobra-cli
- Mature ecosystem with Viper integration
Cons
- May be overkill for small projects (lighter alternatives like urfave/cli exist)
- Somewhat complex initial setup (high learning cost especially for beginners)
- Dependency on pflag library (incompatible with standard flag package)
- Different API from standard flag library
- Complexity due to many features (understanding all features can be overwhelming)
Key Links
Code Examples
Basic Structure
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "MyApp is an awesome CLI application",
Long: `MyApp is an awesome CLI application that provides
various useful features.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello from MyApp!")
},
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Subcommands and Flags Example
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var (
verbose bool
source string
)
func main() {
var rootCmd = &cobra.Command{
Use: "app",
Short: "A brief description of your application",
}
// Global flag
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
// serve subcommand
var port int
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Start the server",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Starting server on port %d...\n", port)
if verbose {
fmt.Println("Verbose mode enabled")
}
},
}
serveCmd.Flags().IntVarP(&port, "port", "p", 8080, "server port")
// config subcommand
var configCmd = &cobra.Command{
Use: "config",
Short: "Manage configuration",
}
var configSetCmd = &cobra.Command{
Use: "set KEY VALUE",
Short: "Set a configuration value",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Setting %s = %s\n", args[0], args[1])
},
}
// Build command hierarchy
rootCmd.AddCommand(serveCmd, configCmd)
configCmd.AddCommand(configSetCmd)
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Dynamic Completion Example
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use: "kubectl",
Short: "Kubernetes cluster manager",
}
var getCmd = &cobra.Command{
Use: "get TYPE [NAME]",
Short: "Display resources",
ValidArgs: []string{"pod", "service", "deployment", "node"},
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Getting %s...\n", args[0])
},
}
// Dynamic completion function
getCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
if len(args) == 0 {
// Resource type completion
return []cobra.Completion{
{CompletionText: "pod", Description: "Pod resources"},
{CompletionText: "service", Description: "Service resources"},
{CompletionText: "deployment", Description: "Deployment resources"},
{CompletionText: "node", Description: "Node resources"},
}, cobra.ShellCompDirectiveNoFileComp
}
if len(args) == 1 {
// Resource name completion (would fetch from actual cluster)
return getResourceNames(args[0], toComplete), cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
// Flag dynamic completion
var outputFormat string
getCmd.Flags().StringVarP(&outputFormat, "output", "o", "json", "Output format")
getCmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
return []cobra.Completion{
{CompletionText: "json", Description: "JSON format"},
{CompletionText: "yaml", Description: "YAML format"},
{CompletionText: "table", Description: "Table format"},
}, cobra.ShellCompDirectiveNoFileComp
})
rootCmd.AddCommand(getCmd)
rootCmd.Execute()
}
func getResourceNames(resourceType, prefix string) []cobra.Completion {
// In real applications, would call API to get resource names
mockResources := map[string][]string{
"pod": {"nginx-pod", "mysql-pod", "redis-pod"},
"service": {"nginx-svc", "mysql-svc", "redis-svc"},
}
var completions []cobra.Completion
if resources, ok := mockResources[resourceType]; ok {
for _, r := range resources {
if strings.HasPrefix(r, prefix) {
completions = append(completions, cobra.Completion{CompletionText: r})
}
}
}
return completions
}
Flag Groups Example
package main
import (
"fmt"
"github.com/spf13/cobra"
)
func main() {
var (
username string
password string
token string
json bool
yaml bool
xml bool
)
rootCmd := &cobra.Command{
Use: "api",
Short: "API client",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Calling API...")
},
}
// Authentication flags
rootCmd.Flags().StringVar(&username, "username", "", "Username")
rootCmd.Flags().StringVar(&password, "password", "", "Password")
rootCmd.Flags().StringVar(&token, "token", "", "Auth token")
// Output format flags
rootCmd.Flags().BoolVar(&json, "json", false, "Output in JSON")
rootCmd.Flags().BoolVar(&yaml, "yaml", false, "Output in YAML")
rootCmd.Flags().BoolVar(&xml, "xml", false, "Output in XML")
// Flag group configuration
rootCmd.MarkFlagsRequiredTogether("username", "password") // username and password required together
rootCmd.MarkFlagsMutuallyExclusive("json", "yaml", "xml") // only one output format
rootCmd.MarkFlagsOneRequired("json", "yaml", "xml") // output format is required
// Only one auth method
rootCmd.MarkFlagsMutuallyExclusive("token", "username")
rootCmd.Execute()
}
Lifecycle Hooks Example
package main
import (
"fmt"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{
Use: "app",
Short: "Application",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("Loading configuration...")
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("Initializing...")
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Running main process...")
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("Cleaning up...")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("Saving logs...")
},
}
rootCmd.Execute()
}
Practical CLI Development Tool Example
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
configFile string
verbose bool
outputFormat string
)
func main() {
rootCmd := &cobra.Command{
Use: "devtool",
Short: "Developer utility tool",
Long: `devtool is a CLI tool designed to streamline daily development tasks.
It provides functionality for project management, file operations, build automation, and more.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initConfig()
if verbose {
fmt.Printf("Config file: %s\n", viper.ConfigFileUsed())
fmt.Printf("Output format: %s\n", outputFormat)
}
},
}
// Global flags
rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.devtool.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "table", "output format (table, json, yaml)")
// Add subcommands
rootCmd.AddCommand(projectCmd(), buildCmd(), deployCmd())
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func projectCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "project",
Short: "Project management commands",
Long: "Initialize projects, create templates, and manage project settings.",
}
initCmd := &cobra.Command{
Use: "init [NAME]",
Short: "Initialize a new project",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
projectName := args[0]
template, _ := cmd.Flags().GetString("template")
force, _ := cmd.Flags().GetBool("force")
if verbose {
fmt.Printf("Initializing project: %s (template: %s)\n", projectName, template)
}
if !force && directoryExists(projectName) {
fmt.Fprintf(os.Stderr, "Error: Directory '%s' already exists. Use --force to overwrite.\n", projectName)
os.Exit(1)
}
createProject(projectName, template)
fmt.Printf("Project '%s' created successfully.\n", projectName)
},
}
initCmd.Flags().StringP("template", "t", "basic", "project template (basic, web, api, cli)")
initCmd.Flags().BoolP("force", "f", false, "overwrite existing directory")
listCmd := &cobra.Command{
Use: "list",
Short: "List available templates",
Run: func(cmd *cobra.Command, args []string) {
templates := []string{"basic", "web", "api", "cli", "microservice"}
fmt.Println("Available templates:")
for _, template := range templates {
fmt.Printf(" - %s\n", template)
}
},
}
cmd.AddCommand(initCmd, listCmd)
return cmd
}
func buildCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "build",
Short: "Build project",
Long: "Build the project and generate artifacts.",
}
var (
target string
optimize bool
parallel int
)
buildCmd := &cobra.Command{
Use: "run [TARGET...]",
Short: "Build specified targets",
Run: func(cmd *cobra.Command, args []string) {
targets := args
if len(targets) == 0 {
targets = []string{"default"}
}
fmt.Printf("Starting build (parallelism: %d, optimize: %t)\n", parallel, optimize)
for _, target := range targets {
if verbose {
fmt.Printf(" Building target '%s'...\n", target)
}
// Simulate build process
time.Sleep(time.Millisecond * 500)
switch outputFormat {
case "json":
fmt.Printf(`{"target": "%s", "status": "success", "duration": "500ms"}%s`, target, "\n")
case "yaml":
fmt.Printf("target: %s\nstatus: success\nduration: 500ms\n---\n", target)
default:
fmt.Printf("✓ %s (500ms)\n", target)
}
}
fmt.Println("Build completed")
},
}
buildCmd.Flags().StringVarP(&target, "target", "t", "", "specify target")
buildCmd.Flags().BoolVarP(&optimize, "optimize", "O", false, "enable optimized build")
buildCmd.Flags().IntVarP(¶llel, "parallel", "j", 4, "number of parallel builds")
cleanCmd := &cobra.Command{
Use: "clean",
Short: "Clean build artifacts",
Run: func(cmd *cobra.Command, args []string) {
if verbose {
fmt.Println("Cleaning build artifacts...")
}
// Simulate cleanup process
time.Sleep(time.Millisecond * 200)
fmt.Println("Cleanup completed")
},
}
cmd.AddCommand(buildCmd, cleanCmd)
return cmd
}
func deployCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy application",
Long: "Deploy the built application to specified environment.",
}
var (
environment string
dryRun bool
confirm bool
)
deployCmd := &cobra.Command{
Use: "run",
Short: "Execute deployment",
PreRun: func(cmd *cobra.Command, args []string) {
if environment == "production" && !confirm {
fmt.Print("Are you sure you want to deploy to production? (y/N): ")
var response string
fmt.Scanln(&response)
if strings.ToLower(response) != "y" && strings.ToLower(response) != "yes" {
fmt.Println("Deployment cancelled")
os.Exit(0)
}
}
},
Run: func(cmd *cobra.Command, args []string) {
if dryRun {
fmt.Printf("[Dry run] Simulating deployment to %s environment\n", environment)
} else {
fmt.Printf("Deploying to %s environment...\n", environment)
}
// Simulate deployment process
steps := []string{"Config validation", "Upload", "Service restart", "Health check"}
for i, step := range steps {
if verbose {
fmt.Printf(" [%d/%d] %s...\n", i+1, len(steps), step)
}
time.Sleep(time.Millisecond * 300)
}
if !dryRun {
fmt.Printf("✓ Deployment to %s environment completed\n", environment)
} else {
fmt.Printf("✓ Dry run completed (no actual deployment was performed)\n")
}
},
}
deployCmd.Flags().StringVarP(&environment, "env", "e", "staging", "deployment environment (staging, production)")
deployCmd.Flags().BoolVarP(&dryRun, "dry-run", "n", false, "dry run mode")
deployCmd.Flags().BoolVarP(&confirm, "yes", "y", false, "skip confirmation prompt")
// Environment validation
deployCmd.MarkFlagRequired("env")
deployCmd.RegisterFlagCompletionFunc("env", func(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {
return []cobra.Completion{
{CompletionText: "staging", Description: "Staging environment"},
{CompletionText: "production", Description: "Production environment"},
{CompletionText: "development", Description: "Development environment"},
}, cobra.ShellCompDirectiveNoFileComp
})
statusCmd := &cobra.Command{
Use: "status",
Short: "Check deployment status",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Current deployment status:\n")
fmt.Printf(" Environment: %s\n", environment)
fmt.Printf(" Last deployment: 2024-01-15 14:30:00\n")
fmt.Printf(" Version: v1.2.3\n")
fmt.Printf(" Status: Healthy\n")
},
}
statusCmd.Flags().StringVarP(&environment, "env", "e", "staging", "environment to check")
cmd.AddCommand(deployCmd, statusCmd)
return cmd
}
func initConfig() {
if configFile != "" {
viper.SetConfigFile(configFile)
} else {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".devtool")
}
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil && verbose {
fmt.Printf("Using config file: %s\n", viper.ConfigFileUsed())
}
}
func directoryExists(path string) bool {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return info.IsDir()
}
func createProject(name, template string) {
// Simulate project creation
os.MkdirAll(name, 0755)
switch template {
case "web":
os.MkdirAll(filepath.Join(name, "static"), 0755)
os.MkdirAll(filepath.Join(name, "templates"), 0755)
case "api":
os.MkdirAll(filepath.Join(name, "handlers"), 0755)
os.MkdirAll(filepath.Join(name, "models"), 0755)
case "cli":
os.MkdirAll(filepath.Join(name, "cmd"), 0755)
}
// Create basic files
os.WriteFile(filepath.Join(name, "README.md"), []byte("# "+name+"\n"), 0644)
os.WriteFile(filepath.Join(name, ".gitignore"), []byte("*.log\n*.tmp\n"), 0644)
}
Cobra-CLI Tool Usage Example
# Install cobra-cli
go install github.com/spf13/cobra-cli@latest
# Initialize new CLI application
cobra-cli init myapp
# Add subcommands
cobra-cli add serve
cobra-cli add config
cobra-cli add create -p 'configCmd'
# Generated file structure
# myapp/
# ├── cmd/
# │ ├── root.go
# │ ├── serve.go
# │ ├── config.go
# │ └── create.go
# ├── main.go
# └── go.mod