This document covers advanced pflag features including flag normalization, deprecation, hidden flags, annotations, and NoOptDefVal.
import "github.com/spf13/pflag"Flag name normalization allows you to define custom rules for matching flag names, enabling features like treating hyphens and underscores as equivalent.
func (f *FlagSet) SetNormalizeFunc(n func(*FlagSet, string) NormalizedName)Set a custom function to normalize flag names. The normalization function is applied to both flag definitions and lookups, allowing flexible name matching.
func (f *FlagSet) GetNormalizeFunc() func(*FlagSet, string) NormalizedNameReturn the previously set normalize function, or an identity function if none was set.
type NormalizedName stringA flag name that has been normalized according to FlagSet rules.
import (
"strings"
"github.com/spf13/pflag"
)
func normalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
// Replace underscores with hyphens
from := []string{"_"}
to := "-"
for _, sep := range from {
name = strings.Replace(name, sep, to, -1)
}
return pflag.NormalizedName(name)
}
func main() {
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.SetNormalizeFunc(normalizeFunc)
fs.String("my-flag", "default", "a flag")
// All of these will match the same flag:
fs.Parse([]string{"--my-flag=value"}) // Works
fs.Parse([]string{"--my_flag=value"}) // Also works
fs.Parse([]string{"--my__flag=value"}) // Also works
}func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "old-flag-name":
name = "new-flag-name"
case "deprecated-name":
name = "current-name"
}
return pflag.NormalizedName(name)
}
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.SetNormalizeFunc(aliasNormalizeFunc)
fs.String("new-flag-name", "default", "a flag")
// Both old and new names work
fs.Parse([]string{"--old-flag-name=value"}) // Works
fs.Parse([]string{"--new-flag-name=value"}) // Also worksfunc (f *FlagSet) MarkDeprecated(name string, usageMessage string) errorMark a flag as deprecated. The flag continues to function but will not appear in help/usage messages. When the deprecated flag is used, the usageMessage is printed.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("config", "default.conf", "configuration file")
fs.String("cfg", "default.conf", "configuration file (use --config instead)")
// Deprecate the old flag
err := fs.MarkDeprecated("cfg", "please use --config instead")
if err != nil {
fmt.Printf("Error marking flag deprecated: %v\n", err)
}
// Now when user runs: myapp --cfg=file.conf
// Output: Flag --cfg has been deprecated, please use --config insteadfunc (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) errorMark the shorthand of a flag as deprecated. The shorthand continues to function but won't appear in help/usage messages. When used, the usageMessage is printed.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.StringP("verbose-output", "v", "normal", "output verbosity")
// Deprecate the shorthand but keep the long form
err := fs.MarkShorthandDeprecated("verbose-output", "please use --verbose-output only")
if err != nil {
fmt.Printf("Error marking shorthand deprecated: %v\n", err)
}
// Now when user runs: myapp -v=high
// Output: Flag shorthand -v has been deprecated, please use --verbose-output onlyfs.String("old-flag", "default", "deprecated flag")
fs.MarkDeprecated("old-flag", "use --new-flag instead")
flag := fs.Lookup("old-flag")
if flag.Deprecated != "" {
fmt.Printf("Flag is deprecated: %s\n", flag.Deprecated)
}
if flag.ShorthandDeprecated != "" {
fmt.Printf("Shorthand is deprecated: %s\n", flag.ShorthandDeprecated)
}func (f *FlagSet) MarkHidden(name string) errorMark a flag as hidden. The flag continues to function normally but will not appear in help/usage messages. Useful for internal or debug flags.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.Bool("debug", false, "enable debug mode")
fs.String("internal-config", "", "internal configuration (hidden)")
// Hide the internal flag from help output
err := fs.MarkHidden("internal-config")
if err != nil {
fmt.Printf("Error marking flag hidden: %v\n", err)
}
fs.Parse(os.Args[1:])
// The flag still works:
config, _ := fs.GetString("internal-config")flag := fs.Lookup("internal-config")
if flag.Hidden {
fmt.Println("This flag is hidden from help text")
}func (f *FlagSet) HasAvailableFlags() boolReturn true if the FlagSet has any flags that are not hidden. Useful for determining whether to show help output.
Usage:
if fs.HasAvailableFlags() {
fmt.Println("Available options:")
fs.PrintDefaults()
} else {
fmt.Println("No user-facing options available")
}Annotations provide extensible metadata for flags, commonly used for shell completion hints and other tooling.
func (f *FlagSet) SetAnnotation(name, key string, values []string) errorAdd an annotation to a flag. Annotations are stored as map[string][]string and can contain arbitrary metadata.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("output", "", "output file")
// Add filename completion hint for bash
err := fs.SetAnnotation("output", "cobra_annotation_bash_completion", []string{"file"})
if err != nil {
fmt.Printf("Error setting annotation: %v\n", err)
}
// Add custom metadata
fs.SetAnnotation("output", "category", []string{"io", "file"})
fs.SetAnnotation("output", "required", []string{"true"})flag := fs.Lookup("output")
if flag.Annotations != nil {
if category, ok := flag.Annotations["category"]; ok {
fmt.Printf("Flag category: %v\n", category)
}
if required, ok := flag.Annotations["required"]; ok {
fmt.Printf("Required: %v\n", required[0] == "true")
}
}// File completion
fs.SetAnnotation("config", "cobra_annotation_bash_completion", []string{"file"})
// Directory completion
fs.SetAnnotation("output-dir", "cobra_annotation_bash_completion", []string{"dir"})
// Custom completion
fs.SetAnnotation("format", "cobra_annotation_bash_completion_custom",
[]string{"__myapp_custom_format_func"})fs.SetAnnotation("port", "group", []string{"server"})
fs.SetAnnotation("host", "group", []string{"server"})
fs.SetAnnotation("timeout", "group", []string{"client"})
// Later, print flags by group
fs.VisitAll(func(flag *pflag.Flag) {
if group, ok := flag.Annotations["group"]; ok {
fmt.Printf("Flag %s belongs to group: %s\n", flag.Name, group[0])
}
})fs.SetAnnotation("port", "min", []string{"1"})
fs.SetAnnotation("port", "max", []string{"65535"})
fs.SetAnnotation("email", "pattern", []string{"^[a-z]+@[a-z]+\\.[a-z]+$"})The NoOptDefVal field allows a flag to have a special default value when the flag is present on the command line without an explicit argument.
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
port := fs.Int("port", 8080, "server port")
// Set NoOptDefVal
portFlag := fs.Lookup("port")
portFlag.NoOptDefVal = "9090"
fs.Parse(args)| Command Line | Result | Explanation |
|---|---|---|
myapp | port = 8080 | No flag, use default |
myapp --port=3000 | port = 3000 | Explicit value |
myapp --port | port = 9090 | Flag without value, use NoOptDefVal |
verbosity := pflag.String("verbosity", "normal", "output verbosity")
pflag.Lookup("verbosity").NoOptDefVal = "high"
// myapp => verbosity = "normal"
// myapp --verbosity=low => verbosity = "low"
// myapp --verbosity => verbosity = "high"port := pflag.Int("port", 0, "server port (0=disabled)")
pflag.Lookup("port").NoOptDefVal = "8080"
// myapp => port = 0 (disabled)
// myapp --port=3000 => port = 3000
// myapp --port => port = 8080 (default when enabled)debug := pflag.String("debug", "off", "debug level (off/basic/verbose)")
pflag.Lookup("debug").NoOptDefVal = "basic"
// myapp => debug = "off"
// myapp --debug=verbose => debug = "verbose"
// myapp --debug => debug = "basic"Configure which parsing errors should be ignored rather than causing parse failures.
type ParseErrorsAllowlist struct {
UnknownFlags bool // Ignore unknown flags errors
}
type FlagSet struct {
ParseErrorsAllowlist ParseErrorsAllowlist
// ...
}fs := pflag.NewFlagSet("app", pflag.ContinueOnError)
fs.Int("known-flag", 0, "a known flag")
// Configure to ignore unknown flags
fs.ParseErrorsAllowlist.UnknownFlags = true
// This will not error even though --unknown-flag is not defined
err := fs.Parse([]string{"--known-flag=42", "--unknown-flag=value"})
if err != nil {
// Only non-ignored errors will be returned
fmt.Printf("Parse error: %v\n", err)
}
// The known flag was still parsed successfully
value, _ := fs.GetInt("known-flag")
fmt.Printf("Known flag: %d\n", value) // Prints: 42Allows older versions of applications to ignore flags added in newer versions:
fs.ParseErrorsAllowlist.UnknownFlags = true
fs.Parse(os.Args[1:])
// Older app ignores newer flags it doesn't understandWhen combining flags from multiple sources (plugins), unknown flags can be tolerated:
// Core app flags
coreFlags := pflag.NewFlagSet("core", pflag.ContinueOnError)
coreFlags.ParseErrorsAllowlist.UnknownFlags = true
coreFlags.String("config", "", "config file")
// Parse all args, ignoring plugin-specific flags
coreFlags.Parse(os.Args[1:])
// Plugins can parse their own flags from the same args
pluginFlags := pflag.NewFlagSet("plugin", pflag.ContinueOnError)
pluginFlags.String("plugin-option", "", "plugin option")
pluginFlags.Parse(os.Args[1:])func UnquoteUsage(flag *Flag) (name string, usage string)Extract a back-quoted name from the usage string for a flag and return it along with the un-quoted usage. This helps in generating more informative help messages.
Usage:
fs := pflag.NewFlagSet("app", pflag.ExitOnError)
fs.String("output", "", "write output to `file`")
flag := fs.Lookup("output")
name, usage := pflag.UnquoteUsage(flag)
fmt.Printf("Name: %s\n", name) // Prints: file
fmt.Printf("Usage: %s\n", usage) // Prints: write output to fileIf no back-quoted name is present, the name is inferred from the flag's type:
fs.Int("count", 0, "number of iterations")
flag := fs.Lookup("count")
name, usage := pflag.UnquoteUsage(flag)
fmt.Printf("Name: %s\n", name) // Prints: int (inferred from type)func ParseIPv4Mask(s string) net.IPMaskParse an IPv4 mask written in IP form (e.g., "255.255.255.0"). This is a utility function that should belong to the net package but is provided here.
Usage:
import "net"
mask := pflag.ParseIPv4Mask("255.255.255.0")
fmt.Printf("Mask: %v\n", mask) // Prints: ffffff00Here's a comprehensive example combining multiple advanced features:
package main
import (
"fmt"
"os"
"strings"
"github.com/spf13/pflag"
)
func main() {
fs := pflag.NewFlagSet("myapp", pflag.ExitOnError)
// Set up normalization for hyphen/underscore equivalence
fs.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
name = strings.ReplaceAll(name, "_", "-")
return pflag.NormalizedName(name)
})
// Define flags
fs.Int("server-port", 8080, "server port")
fs.String("config-file", "default.conf", "configuration file")
fs.String("old-config", "", "deprecated configuration file")
fs.Bool("debug-mode", false, "enable debug output")
fs.String("internal-token", "", "internal auth token")
// Mark deprecated flag
fs.MarkDeprecated("old-config", "please use --config-file instead")
// Hide internal flag
fs.MarkHidden("internal-token")
// Set up NoOptDefVal for debug
fs.Lookup("debug-mode").NoOptDefVal = "true"
// Add annotations for tooling
fs.SetAnnotation("config-file", "cobra_annotation_bash_completion", []string{"file"})
fs.SetAnnotation("config-file", "required", []string{"true"})
// Allow unknown flags for forward compatibility
fs.ParseErrorsAllowlist.UnknownFlags = true
// Parse
fs.Parse(os.Args[1:])
// Access values
port, _ := fs.GetInt("server-port")
config, _ := fs.GetString("config-file")
debug, _ := fs.GetBool("debug-mode")
fmt.Printf("Starting server:\n")
fmt.Printf(" Port: %d\n", port)
fmt.Printf(" Config: %s\n", config)
fmt.Printf(" Debug: %v\n", debug)
// Check for required flags
if flag := fs.Lookup("config-file"); !flag.Changed {
if annotations, ok := flag.Annotations["required"]; ok && annotations[0] == "true" {
fmt.Fprintln(os.Stderr, "Error: --config-file is required")
os.Exit(1)
}
}
}