Typed enumeration flags (single and slice) for spf13/pflag, Go's flag drop-in package.
Go
35
56 commits
updated Jun 5, 2026
enumflag/v2 is a Golang package which supplements the Golang CLI flag packages
spf13/cobra and
spf13/pflag with enumeration flags, including
support for enumeration slices. Thanks to Go generics, enumflag/v2 now
provides type-safe enumeration flags (and thus requires Go 1.18 or later).
The v2 API is source-compatible with v0 unless you've used the
Get()method in the past. However, since the use of Go generics might be a breaking change to downstream projects the semantic major version ofenumflagthus went from v0 straight to v2.
For instance, users can specify enum flags as --mode=foo or --mode=bar,
where foo and bar are valid enumeration values. Other values which are not
part of the set of allowed enumeration values cannot be set and raise CLI flag
errors. In case of an enumeration slice flag users can specify multiple
enumeration values either with a single flag --mode=foo,bar or multiple flag
calls, such as --mode=foo --mode=bar.
Application programmers then simply deal with enumeration values in form of
uints (or ints, erm, anything that satisfies comparables),
liberated from parsing strings and validating enumeration flags.
For devcontainer instructions, please see the section "DevContainer" below.
In case you are just interested in string-based one-of-a-set flags, then the following packages offer you a minimalist approach:
But if you instead want to handle one-of-a-set flags as properly typed enumerations instead of strings, or if you need (multiple-of-a-set) slice support, then please read on.
To add enumflag/v2 as a dependency, in your Go module issue:
go get github.com/thediveo/enumflag/v2
Without further ado, here's how to define and use enum flags in your own applications...
import (
"fmt"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies comparable.
type FooMode enumflag.Flag
// ② Define the enumeration values for FooMode.
const (
Foo FooMode = iota
Bar
)
// ③ Map enumeration values to their textual representations (value
// identifiers).
var FooModeIds = map[FooMode][]string{
Foo: {"foo"},
Bar: {"bar"},
}
// ④ Now use the FooMode enum flag. If you want a non-zero default, then
// simply set it here, such as in "foomode = Bar".
var foomode FooMode
func main() {
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("mode is: %d=%q\n",
foomode,
cmd.PersistentFlags().Lookup("mode").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().VarP(
enumflag.New(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
rootCmd.SetArgs([]string{"--mode", "bAr"})
_ = rootCmd.Execute()
}
The boilerplate pattern is always the same:
type FooMode enumflag.Flag.var foomode = Bar. It will be used correctly.Dynamic flag completion can be enabled by calling the RegisterCompletion(...)
receiver of an enum flag (more precise: flag value) created using
enumflag.New(...). enumflag supports dynamic flag completion for both scalar
and slice enum flags. Unfortunately, due to the cobra API design it isn't
possible for enumflag to offer a fluent API. Instead, creation, adding, and
registering have to be carried out as separate instructions.
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
ef := enumflag.New(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive)
rootCmd.PersistentFlags().VarP(
ef,
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
// ⑥ register completion
ef.RegisterCompletion(rootCmd, "mode", enumflag.Help[FooMode]{
Foo: "foos the output",
Bar: "bars the output",
})
Please note for shell completion to work, your root command needs to have at
least one (explicit) sub command. Otherwise, cobra won't automatically add an
additional completion sub command. For more details, please refer to cobra's
documentation on Generating shellcompletions.
A typical example might be your application using a 3rd party logging package
and you want to offer a -v log level CLI flag. Here, we use the existing 3rd
party enum values and set a non-zero default for our logging CLI flag.
Considering the boiler plate shown above, we can now leave out steps ① and ②, because these definitions come from a 3rd party package. We only need to supply the textual enum names as ③.
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
func main() {
// ①+② skip "define your own enum flag type" and enumeration values, as we
// already have a 3rd party one.
// ③ Map 3rd party enumeration values to their textual representations
var LoglevelIds = map[log.Level][]string{
log.TraceLevel: {"trace"},
log.DebugLevel: {"debug"},
log.InfoLevel: {"info"},
log.WarnLevel: {"warning", "warn"},
log.ErrorLevel: {"error"},
log.FatalLevel: {"fatal"},
log.PanicLevel: {"panic"},
}
// ④ Define your enum flag value and set the your logging default value.
var loglevel log.Level = log.WarnLevel
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("logging level is: %d=%q\n",
loglevel,
cmd.PersistentFlags().Lookup("log").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().Var(
enumflag.New(&loglevel, "log", LoglevelIds, enumflag.EnumCaseInsensitive),
"log",
"sets logging level; can be 'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'panic'")
// Defaults to what we set above: warn level.
_ = rootCmd.Execute()
// User specifies a specific level, such as log level.
rootCmd.SetArgs([]string{"--log", "debug"})
_ = rootCmd.Execute()
}
Sometimes you might want a CLI enum flag to have a default value when the user
just specifies the CLI flag without its value. A good example is the
--color flag of the ls command:
--color without a value, it
will default to the value of auto;--color=always,--color=never,--color=auto.In such situations, use spf13/pflags's
NoOptDefVal to set the
flag's default value as text, if the flag is on the command line without any
options.
The gist here is as follows, please see also colormode.go from my lxkns Linux namespaces discovery project:
rootCmd.PersistentFlags().VarP(
enumflag.New(&colorize, "color", colorModeIds, enumflag.EnumCaseSensitive),
"color", "c",
"colorize the output; can be 'always' (default if omitted), 'auto',\n"+
"or 'never'")
rootCmd.PersistentFlags().Lookup("color").NoOptDefVal = "always"
In other situations you might not want to have a default value set, because a particular CLI flag is mandatory (using cobra's MarkFlagRequired). Here, cobra's help should not show a (useless) default enum flag setting but only the available enum values.
Don't assign the zero value of your enum type to any value, except the "non-existing" default.
// ② Define the enumeration values for FooMode; do not assign the zero value to
// any enum value except for the "no default" default.
const (
NoDefault FooMode = iota // optional; must be the zero value.
Foo // make sure to not use the zero value.
Bar
)
Also, don't map the zero value of your enum type.
// ③ Map enumeration values to their textual representations (value
// identifiers).
var FooModeIds = map[FooMode][]string{
// ...do NOT include/map the "no default" zero value!
Foo: {"foo"},
Bar: {"bar"},
}
Finally, simply use enumflag.NewWithoutDefault instead of enumflag.New –
that's all.
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().VarP(
enumflag.NewWithoutDefault(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
For a slice of enumerations, simply declare your variable to be a slice of your
enumeration type and then use enumflag.NewSlice(...) instead of
enumflag.New(...).
import (
"fmt"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies comparable.
type MooMode enumflag.Flag
// ② Define the enumeration values for FooMode.
const (
Moo MooMode = (iota + 1) * 111
Møø
Mimimi
)
// ③ Map enumeration values to their textual representations (value
// identifiers).
var MooModeIds = map[MooMode][]string{
Moo: {"moo"},
Møø: {"møø"},
Mimimi: {"mimimi"},
}
func Example_slice() {
// ④ Define your enum slice flag value.
var moomode []MooMode
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("mode is: %d=%q\n",
moomode,
cmd.PersistentFlags().Lookup("mode").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enumm slice flag.
rootCmd.PersistentFlags().VarP(
enumflag.NewSlice(&moomode, "mode", MooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"can be any combination of 'moo', 'møø', 'mimimi'")
rootCmd.SetArgs([]string{"--mode", "Moo,møø"})
_ = rootCmd.Execute()
}
[!CAUTION]
Do not use VSCode's "
Dev Containers: Clone Repository in Container Volume" command, as it is utterly broken by design, ignoring.devcontainer/devcontainer.json.
git clone https://github.com/thediveo/enumflagenumflag.code-workspace and off you go...netdb supports versions of Go that are noted by the Go release
policy, that is, major
versions N and N-1 (where N is the current major version).
Please see CONTRIBUTING.md.
lxkns is Copyright 2020, 2026 Harald Albrecht, and licensed under the Apache
License, Version 2.0.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
37 followers · starred Mar 2024
39 followers · starred Dec 2023
500 followers · starred Nov 2023
Typed enumeration flags (single and slice) for spf13/pflag, Go's flag drop-in package.
Go
35
56 commits
updated Jun 5, 2026
enumflag/v2 is a Golang package which supplements the Golang CLI flag packages
spf13/cobra and
spf13/pflag with enumeration flags, including
support for enumeration slices. Thanks to Go generics, enumflag/v2 now
provides type-safe enumeration flags (and thus requires Go 1.18 or later).
The v2 API is source-compatible with v0 unless you've used the
Get()method in the past. However, since the use of Go generics might be a breaking change to downstream projects the semantic major version ofenumflagthus went from v0 straight to v2.
For instance, users can specify enum flags as --mode=foo or --mode=bar,
where foo and bar are valid enumeration values. Other values which are not
part of the set of allowed enumeration values cannot be set and raise CLI flag
errors. In case of an enumeration slice flag users can specify multiple
enumeration values either with a single flag --mode=foo,bar or multiple flag
calls, such as --mode=foo --mode=bar.
Application programmers then simply deal with enumeration values in form of
uints (or ints, erm, anything that satisfies comparables),
liberated from parsing strings and validating enumeration flags.
For devcontainer instructions, please see the section "DevContainer" below.
In case you are just interested in string-based one-of-a-set flags, then the following packages offer you a minimalist approach:
But if you instead want to handle one-of-a-set flags as properly typed enumerations instead of strings, or if you need (multiple-of-a-set) slice support, then please read on.
To add enumflag/v2 as a dependency, in your Go module issue:
go get github.com/thediveo/enumflag/v2
Without further ado, here's how to define and use enum flags in your own applications...
import (
"fmt"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies comparable.
type FooMode enumflag.Flag
// ② Define the enumeration values for FooMode.
const (
Foo FooMode = iota
Bar
)
// ③ Map enumeration values to their textual representations (value
// identifiers).
var FooModeIds = map[FooMode][]string{
Foo: {"foo"},
Bar: {"bar"},
}
// ④ Now use the FooMode enum flag. If you want a non-zero default, then
// simply set it here, such as in "foomode = Bar".
var foomode FooMode
func main() {
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("mode is: %d=%q\n",
foomode,
cmd.PersistentFlags().Lookup("mode").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().VarP(
enumflag.New(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
rootCmd.SetArgs([]string{"--mode", "bAr"})
_ = rootCmd.Execute()
}
The boilerplate pattern is always the same:
type FooMode enumflag.Flag.var foomode = Bar. It will be used correctly.Dynamic flag completion can be enabled by calling the RegisterCompletion(...)
receiver of an enum flag (more precise: flag value) created using
enumflag.New(...). enumflag supports dynamic flag completion for both scalar
and slice enum flags. Unfortunately, due to the cobra API design it isn't
possible for enumflag to offer a fluent API. Instead, creation, adding, and
registering have to be carried out as separate instructions.
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
ef := enumflag.New(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive)
rootCmd.PersistentFlags().VarP(
ef,
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
// ⑥ register completion
ef.RegisterCompletion(rootCmd, "mode", enumflag.Help[FooMode]{
Foo: "foos the output",
Bar: "bars the output",
})
Please note for shell completion to work, your root command needs to have at
least one (explicit) sub command. Otherwise, cobra won't automatically add an
additional completion sub command. For more details, please refer to cobra's
documentation on Generating shellcompletions.
A typical example might be your application using a 3rd party logging package
and you want to offer a -v log level CLI flag. Here, we use the existing 3rd
party enum values and set a non-zero default for our logging CLI flag.
Considering the boiler plate shown above, we can now leave out steps ① and ②, because these definitions come from a 3rd party package. We only need to supply the textual enum names as ③.
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
func main() {
// ①+② skip "define your own enum flag type" and enumeration values, as we
// already have a 3rd party one.
// ③ Map 3rd party enumeration values to their textual representations
var LoglevelIds = map[log.Level][]string{
log.TraceLevel: {"trace"},
log.DebugLevel: {"debug"},
log.InfoLevel: {"info"},
log.WarnLevel: {"warning", "warn"},
log.ErrorLevel: {"error"},
log.FatalLevel: {"fatal"},
log.PanicLevel: {"panic"},
}
// ④ Define your enum flag value and set the your logging default value.
var loglevel log.Level = log.WarnLevel
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("logging level is: %d=%q\n",
loglevel,
cmd.PersistentFlags().Lookup("log").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().Var(
enumflag.New(&loglevel, "log", LoglevelIds, enumflag.EnumCaseInsensitive),
"log",
"sets logging level; can be 'trace', 'debug', 'info', 'warn', 'error', 'fatal', 'panic'")
// Defaults to what we set above: warn level.
_ = rootCmd.Execute()
// User specifies a specific level, such as log level.
rootCmd.SetArgs([]string{"--log", "debug"})
_ = rootCmd.Execute()
}
Sometimes you might want a CLI enum flag to have a default value when the user
just specifies the CLI flag without its value. A good example is the
--color flag of the ls command:
--color without a value, it
will default to the value of auto;--color=always,--color=never,--color=auto.In such situations, use spf13/pflags's
NoOptDefVal to set the
flag's default value as text, if the flag is on the command line without any
options.
The gist here is as follows, please see also colormode.go from my lxkns Linux namespaces discovery project:
rootCmd.PersistentFlags().VarP(
enumflag.New(&colorize, "color", colorModeIds, enumflag.EnumCaseSensitive),
"color", "c",
"colorize the output; can be 'always' (default if omitted), 'auto',\n"+
"or 'never'")
rootCmd.PersistentFlags().Lookup("color").NoOptDefVal = "always"
In other situations you might not want to have a default value set, because a particular CLI flag is mandatory (using cobra's MarkFlagRequired). Here, cobra's help should not show a (useless) default enum flag setting but only the available enum values.
Don't assign the zero value of your enum type to any value, except the "non-existing" default.
// ② Define the enumeration values for FooMode; do not assign the zero value to
// any enum value except for the "no default" default.
const (
NoDefault FooMode = iota // optional; must be the zero value.
Foo // make sure to not use the zero value.
Bar
)
Also, don't map the zero value of your enum type.
// ③ Map enumeration values to their textual representations (value
// identifiers).
var FooModeIds = map[FooMode][]string{
// ...do NOT include/map the "no default" zero value!
Foo: {"foo"},
Bar: {"bar"},
}
Finally, simply use enumflag.NewWithoutDefault instead of enumflag.New –
that's all.
// ⑤ Define the CLI flag parameters for your wrapped enum flag.
rootCmd.PersistentFlags().VarP(
enumflag.NewWithoutDefault(&foomode, "mode", FooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"foos the output; can be 'foo' or 'bar'")
For a slice of enumerations, simply declare your variable to be a slice of your
enumeration type and then use enumflag.NewSlice(...) instead of
enumflag.New(...).
import (
"fmt"
"github.com/spf13/cobra"
"github.com/thediveo/enumflag/v2"
)
// ① Define your new enum flag type. It can be derived from enumflag.Flag,
// but it doesn't need to be as long as it satisfies comparable.
type MooMode enumflag.Flag
// ② Define the enumeration values for FooMode.
const (
Moo MooMode = (iota + 1) * 111
Møø
Mimimi
)
// ③ Map enumeration values to their textual representations (value
// identifiers).
var MooModeIds = map[MooMode][]string{
Moo: {"moo"},
Møø: {"møø"},
Mimimi: {"mimimi"},
}
func Example_slice() {
// ④ Define your enum slice flag value.
var moomode []MooMode
rootCmd := &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
fmt.Printf("mode is: %d=%q\n",
moomode,
cmd.PersistentFlags().Lookup("mode").Value.String())
},
}
// ⑤ Define the CLI flag parameters for your wrapped enumm slice flag.
rootCmd.PersistentFlags().VarP(
enumflag.NewSlice(&moomode, "mode", MooModeIds, enumflag.EnumCaseInsensitive),
"mode", "m",
"can be any combination of 'moo', 'møø', 'mimimi'")
rootCmd.SetArgs([]string{"--mode", "Moo,møø"})
_ = rootCmd.Execute()
}
[!CAUTION]
Do not use VSCode's "
Dev Containers: Clone Repository in Container Volume" command, as it is utterly broken by design, ignoring.devcontainer/devcontainer.json.
git clone https://github.com/thediveo/enumflagenumflag.code-workspace and off you go...netdb supports versions of Go that are noted by the Go release
policy, that is, major
versions N and N-1 (where N is the current major version).
Please see CONTRIBUTING.md.
lxkns is Copyright 2020, 2026 Harald Albrecht, and licensed under the Apache
License, Version 2.0.
Overall
Maintained
Code review
Go module
Latest release
Releases this year
37 followers · starred Mar 2024
39 followers · starred Dec 2023
500 followers · starred Nov 2023