Flags
Master flag types, validation, and advanced flag configuration
Flags
Flags provide a powerful way to accept command-line options in your CLI applications. The Forge CLI framework supports multiple flag types with validation, default values, and flexible configuration options.
Flag Types
String Flags
Accept string values with optional validation:
// Basic string flag
nameFlag := cli.NewStringFlag(
"name", // Flag name
"n", // Short alias
"User name", // Description
"", // Default value
)
// String flag with validation
emailFlag := cli.NewStringFlag(
"email",
"e",
"Email address",
"",
cli.WithValidator(func(value string) error {
if !strings.Contains(value, "@") {
return fmt.Errorf("invalid email format")
}
return nil
}),
)
// String flag with choices
envFlag := cli.NewStringFlag(
"environment",
"env",
"Deployment environment",
"development",
cli.WithChoices([]string{"development", "staging", "production"}),
)Integer Flags
Accept integer values with range validation:
// Basic integer flag
portFlag := cli.NewIntFlag(
"port",
"p",
"Server port",
8080,
)
// Integer flag with range validation
workersFlag := cli.NewIntFlag(
"workers",
"w",
"Number of workers",
4,
cli.WithRange(1, 100), // Min: 1, Max: 100
)
// Integer flag with custom validation
timeoutFlag := cli.NewIntFlag(
"timeout",
"t",
"Timeout in seconds",
30,
cli.WithValidator(func(value int) error {
if value <= 0 {
return fmt.Errorf("timeout must be positive")
}
if value > 3600 {
return fmt.Errorf("timeout cannot exceed 1 hour")
}
return nil
}),
)Boolean Flags
Accept true/false values:
// Basic boolean flag
verboseFlag := cli.NewBoolFlag(
"verbose",
"v",
"Enable verbose output",
false, // Default value
)
// Boolean flag with custom behavior
forceFlag := cli.NewBoolFlag(
"force",
"f",
"Force operation without confirmation",
false,
)
// Boolean flag that can be negated
debugFlag := cli.NewBoolFlag(
"debug",
"d",
"Enable debug mode",
false,
cli.WithNegatable(true), // Allows --no-debug
)Slice Flags
Accept multiple values:
// String slice flag
tagsFlag := cli.NewStringSliceFlag(
"tags",
"t",
"Tags to apply",
[]string{}, // Default empty slice
)
// Integer slice flag
portsFlag := cli.NewIntSliceFlag(
"ports",
"p",
"Ports to expose",
[]int{8080},
)
// Usage examples:
// --tags tag1 --tags tag2 --tags tag3
// --tags tag1,tag2,tag3
// --ports 8080 --ports 9090
// --ports 8080,9090,3000Duration Flags
Accept time duration values:
timeoutFlag := cli.NewDurationFlag(
"timeout",
"t",
"Request timeout",
30*time.Second,
)
intervalFlag := cli.NewDurationFlag(
"interval",
"i",
"Polling interval",
5*time.Minute,
cli.WithValidator(func(d time.Duration) error {
if d < time.Second {
return fmt.Errorf("interval must be at least 1 second")
}
return nil
}),
)
// Usage examples:
// --timeout 30s
// --timeout 5m
// --timeout 1h30m
// --interval 500msFloat Flags
Accept floating-point values:
ratioFlag := cli.NewFloat64Flag(
"ratio",
"r",
"Scaling ratio",
1.0,
cli.WithRange(0.1, 10.0),
)
percentFlag := cli.NewFloat64Flag(
"percent",
"p",
"Success percentage",
95.0,
cli.WithValidator(func(value float64) error {
if value < 0 || value > 100 {
return fmt.Errorf("percentage must be between 0 and 100")
}
return nil
}),
)Flag Configuration
Flag Options
Configure flags with various options:
flag := cli.NewStringFlag(
"config",
"c",
"Configuration file path",
"config.yaml",
// Validation options
cli.WithRequired(true), // Flag is required
cli.WithChoices([]string{"dev", "prod"}), // Limit to specific values
cli.WithValidator(customValidator), // Custom validation function
// Behavior options
cli.WithHidden(false), // Visibility in help
cli.WithEnvVar("CONFIG_FILE"), // Environment variable binding
cli.WithFileCompletion(), // File path completion
// Documentation options
cli.WithUsage("Path to configuration file"),
cli.WithExample("--config /path/to/config.yaml"),
)Environment Variable Binding
Bind flags to environment variables:
// Flag can be set via --port or PORT environment variable
portFlag := cli.NewIntFlag(
"port",
"p",
"Server port",
8080,
cli.WithEnvVar("PORT"),
)
// Multiple environment variable names
dbUrlFlag := cli.NewStringFlag(
"database-url",
"db",
"Database connection URL",
"",
cli.WithEnvVars([]string{"DATABASE_URL", "DB_URL"}),
)Required Flags
Make flags mandatory:
cmd := cli.NewCommand(
"deploy",
"Deploy application",
func(ctx cli.CommandContext) error {
// These values are guaranteed to be present
env := ctx.String("environment")
version := ctx.String("version")
ctx.Info(fmt.Sprintf("Deploying version %s to %s", version, env))
return nil
},
cli.WithFlag(cli.NewStringFlag(
"environment", "e", "Target environment", "",
cli.WithRequired(true),
cli.WithChoices([]string{"staging", "production"}),
)),
cli.WithFlag(cli.NewStringFlag(
"version", "v", "Version to deploy", "",
cli.WithRequired(true),
)),
)Flag Validation
Built-in Validators
Use built-in validation functions:
// Range validation for numbers
ageFlag := cli.NewIntFlag(
"age", "a", "User age", 0,
cli.WithRange(0, 150),
)
// Choice validation for strings
levelFlag := cli.NewStringFlag(
"level", "l", "Log level", "info",
cli.WithChoices([]string{"debug", "info", "warn", "error"}),
)
// Pattern validation with regex
usernameFlag := cli.NewStringFlag(
"username", "u", "Username", "",
cli.WithPattern(`^[a-zA-Z0-9_]{3,20}$`),
)Custom Validators
Create custom validation logic:
// File existence validator
configFlag := cli.NewStringFlag(
"config", "c", "Config file", "",
cli.WithValidator(func(path string) error {
if path == "" {
return nil // Allow empty if not required
}
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("config file does not exist: %s", path)
}
return nil
}),
)
// URL validation
urlFlag := cli.NewStringFlag(
"url", "u", "API URL", "",
cli.WithValidator(func(value string) error {
if _, err := url.Parse(value); err != nil {
return fmt.Errorf("invalid URL: %v", err)
}
return nil
}),
)
// Complex validation with multiple conditions
passwordFlag := cli.NewStringFlag(
"password", "p", "Password", "",
cli.WithValidator(func(password string) error {
if len(password) < 8 {
return fmt.Errorf("password must be at least 8 characters")
}
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasDigit := regexp.MustCompile(`\d`).MatchString(password)
if !hasUpper || !hasLower || !hasDigit {
return fmt.Errorf("password must contain uppercase, lowercase, and digit")
}
return nil
}),
)Accessing Flag Values
In Command Handlers
Access parsed flag values in command handlers:
func deployHandler(ctx cli.CommandContext) error {
// String flags
environment := ctx.String("environment")
version := ctx.String("version")
// Integer flags
replicas := ctx.Int("replicas")
port := ctx.Int("port")
// Boolean flags
dryRun := ctx.Bool("dry-run")
verbose := ctx.Bool("verbose")
// Slice flags
tags := ctx.StringSlice("tags")
ports := ctx.IntSlice("ports")
// Duration flags
timeout := ctx.Duration("timeout")
// Float flags
ratio := ctx.Float64("ratio")
// Check if flag was explicitly set
if ctx.IsSet("custom-config") {
config := ctx.String("custom-config")
// Use custom config
}
return nil
}Default Value Handling
Handle default values and flag presence:
func configHandler(ctx cli.CommandContext) error {
// Get value with fallback
configFile := ctx.String("config")
if configFile == "" {
configFile = "default.yaml"
}
// Check if flag was explicitly provided
if ctx.IsSet("debug") {
// Debug flag was explicitly set
debug := ctx.Bool("debug")
if debug {
ctx.Info("Debug mode enabled")
}
}
// Get all set flags
setFlags := ctx.SetFlags()
ctx.Info(fmt.Sprintf("Flags set: %v", setFlags))
return nil
}Advanced Flag Features
Flag Groups
Group related flags together:
// Database connection flags
dbFlags := []cli.Flag{
cli.NewStringFlag("db-host", "", "Database host", "localhost"),
cli.NewIntFlag("db-port", "", "Database port", 5432),
cli.NewStringFlag("db-name", "", "Database name", ""),
cli.NewStringFlag("db-user", "", "Database user", ""),
cli.NewStringFlag("db-password", "", "Database password", ""),
}
// Server configuration flags
serverFlags := []cli.Flag{
cli.NewIntFlag("port", "p", "Server port", 8080),
cli.NewStringFlag("host", "h", "Server host", "0.0.0.0"),
cli.NewBoolFlag("tls", "", "Enable TLS", false),
}
cmd := cli.NewCommand(
"start",
"Start the server",
startHandler,
cli.WithFlags(append(dbFlags, serverFlags...)),
)Conditional Flags
Make flags conditional based on other flags:
cmd := cli.NewCommand(
"backup",
"Create backup",
func(ctx cli.CommandContext) error {
backupType := ctx.String("type")
if backupType == "remote" {
// Validate remote-specific flags
if ctx.String("remote-url") == "" {
return fmt.Errorf("--remote-url is required for remote backups")
}
}
if backupType == "encrypted" {
if ctx.String("encryption-key") == "" {
return fmt.Errorf("--encryption-key is required for encrypted backups")
}
}
return nil
},
cli.WithFlag(cli.NewStringFlag(
"type", "t", "Backup type", "local",
cli.WithChoices([]string{"local", "remote", "encrypted"}),
)),
cli.WithFlag(cli.NewStringFlag(
"remote-url", "", "Remote backup URL", "",
)),
cli.WithFlag(cli.NewStringFlag(
"encryption-key", "", "Encryption key", "",
)),
)Flag Aliases
Create multiple aliases for flags:
verboseFlag := cli.NewStringFlag(
"verbosity",
"v",
"Verbosity level",
"info",
cli.WithAliases([]string{"verbose", "log-level"}),
cli.WithChoices([]string{"debug", "info", "warn", "error"}),
)
// All of these work:
// --verbosity debug
// --verbose debug
// --log-level debug
// -v debugGlobal Flags
Application-wide Flags
Define flags that apply to all commands:
app := cli.New(cli.Config{
Name: "mytool",
GlobalFlags: []cli.Flag{
cli.NewBoolFlag("verbose", "v", "Verbose output", false),
cli.NewStringFlag("config", "c", "Config file", "config.yaml"),
cli.NewBoolFlag("debug", "d", "Debug mode", false),
},
})
// Global flags are available in all commands
cmd := cli.NewCommand(
"deploy",
"Deploy application",
func(ctx cli.CommandContext) error {
if ctx.Bool("verbose") {
ctx.Info("Verbose mode enabled")
}
configFile := ctx.String("config")
// Use config file
return nil
},
)Persistent Flags
Flags that persist to subcommands:
parentCmd := cli.NewCommand(
"database",
"Database operations",
nil,
cli.WithFlag(cli.NewStringFlag(
"connection", "c", "Database connection string", "",
cli.WithPersistent(true), // Available to all subcommands
)),
)
subCmd := cli.NewCommand(
"migrate",
"Run migrations",
func(ctx cli.CommandContext) error {
// Can access parent's persistent flags
connection := ctx.String("connection")
return nil
},
)
parentCmd.AddSubcommand(subCmd)Best Practices
Consistent Naming
// Use kebab-case for multi-word flags
cli.NewStringFlag("database-url", "db", "Database URL", "")
cli.NewBoolFlag("dry-run", "", "Dry run mode", false)
cli.NewIntFlag("max-connections", "", "Maximum connections", 10)Meaningful Descriptions
// Good: Clear and specific
cli.NewStringFlag("output", "o", "Output format (json, yaml, table)", "table")
// Bad: Vague
cli.NewStringFlag("output", "o", "Output", "table")Sensible Defaults
// Provide reasonable defaults
cli.NewIntFlag("timeout", "t", "Request timeout in seconds", 30)
cli.NewStringFlag("log-level", "l", "Log level", "info")
cli.NewBoolFlag("color", "", "Enable colored output", true)Validation
// Always validate critical flags
portFlag := cli.NewIntFlag(
"port", "p", "Server port", 8080,
cli.WithRange(1, 65535),
)
urlFlag := cli.NewStringFlag(
"api-url", "u", "API endpoint URL", "",
cli.WithValidator(func(value string) error {
if _, err := url.Parse(value); err != nil {
return fmt.Errorf("invalid URL: %v", err)
}
return nil
}),
)Environment Variables
// Bind important flags to environment variables
cli.NewStringFlag(
"database-url", "db", "Database connection URL", "",
cli.WithEnvVar("DATABASE_URL"),
cli.WithRequired(true),
)Examples
Complete Command with Flags
deployCmd := cli.NewCommand(
"deploy",
"Deploy application to specified environment",
func(ctx cli.CommandContext) error {
// Required flags
environment := ctx.String("environment")
version := ctx.String("version")
// Optional flags with defaults
replicas := ctx.Int("replicas")
timeout := ctx.Duration("timeout")
// Boolean flags
dryRun := ctx.Bool("dry-run")
verbose := ctx.Bool("verbose")
// Slice flags
tags := ctx.StringSlice("tags")
if verbose {
ctx.Info(fmt.Sprintf("Deploying version %s to %s", version, environment))
ctx.Info(fmt.Sprintf("Replicas: %d, Timeout: %v", replicas, timeout))
if len(tags) > 0 {
ctx.Info(fmt.Sprintf("Tags: %v", tags))
}
}
if dryRun {
ctx.Warning("DRY RUN: No actual deployment will occur")
return nil
}
// Perform deployment
progress := ctx.ProgressBar(100)
for i := 0; i <= 100; i += 10 {
progress.Set(i)
time.Sleep(200 * time.Millisecond)
}
progress.Finish("Deployment complete!")
return nil
},
// Required flags
cli.WithFlag(cli.NewStringFlag(
"environment", "e", "Target environment", "",
cli.WithRequired(true),
cli.WithChoices([]string{"staging", "production"}),
)),
cli.WithFlag(cli.NewStringFlag(
"version", "v", "Version to deploy", "",
cli.WithRequired(true),
cli.WithPattern(`^v\d+\.\d+\.\d+$`),
)),
// Optional flags
cli.WithFlag(cli.NewIntFlag(
"replicas", "r", "Number of replicas", 3,
cli.WithRange(1, 10),
)),
cli.WithFlag(cli.NewDurationFlag(
"timeout", "t", "Deployment timeout", 5*time.Minute,
)),
cli.WithFlag(cli.NewStringSliceFlag(
"tags", "", "Deployment tags", []string{},
)),
// Boolean flags
cli.WithFlag(cli.NewBoolFlag(
"dry-run", "", "Perform dry run without actual deployment", false,
)),
cli.WithFlag(cli.NewBoolFlag(
"verbose", "", "Enable verbose output", false,
)),
)Next Steps
How is this guide?
Last updated on