Subcommand extensions
k6 provides a rich set of built-in commands, but some use cases require custom CLI tools that integrate with k6’s runtime and state. Subcommand extensions allow you to register custom commands under the k6 x namespace, providing a standardized way to extend k6’s CLI functionality.
Subcommand extensions are useful for:
- Setup and configuration tools (for example, verifying system requirements)
- Custom validation and testing utilities
- Integration tools that interact with k6’s runtime state
- Helper commands specific to your testing infrastructure
You can use registered subcommand extensions directly without building a custom binary, or build a custom k6 binary for extensions you’re developing or that aren’t available in the registry.
Before you begin
To run this tutorial, you’ll need the following applications installed:
You also need to install xk6:
go install go.k6.io/xk6/cmd/xk6@latestWrite a simple extension
Set up a directory to work in.
mkdir xk6-subcommand-mytool; cd xk6-subcommand-mytool; go mod init xk6-subcommand-mytoolThe core of a subcommand extension is a constructor function that creates a Cobra command. The constructor receives k6’s
GlobalStatefor read-only access to runtime configuration.Create an example command named
mytool:package mytool import ( "github.com/spf13/cobra" "go.k6.io/k6/cmd/state" "go.k6.io/k6/subcommand" ) func init() { subcommand.RegisterExtension("mytool", newCommand) } func newCommand(gs *state.GlobalState) *cobra.Command { return &cobra.Command{ Use: "mytool", Short: "My custom tool", Long: "A custom tool that integrates with k6", Run: func(cmd *cobra.Command, args []string) { gs.Logger.Info("Running mytool") // Custom logic here }, } }The extension uses the
subcommand.RegisterExtensionfunction to register itself during initialization. The first argument is the command name (which must match the command’sUsefield), and the second is the constructor function.
Caution
The
GlobalStateprovided to your command is read-only. Do not modify it, as this can cause core k6 instability.
Use automatic extension resolution
If your subcommand extension is registered in the k6 extension catalog, you can use it directly without building a custom binary. k6 automatically detects, builds, and loads the extension when you invoke it:
k6 x mytoolThis works for any registered subcommand extension. k6 provisions the required extension transparently and executes your command.
Note
To use community extensions you must have
K6_ENABLE_COMMUNITY_EXTENSIONSset totrue.K6_ENABLE_COMMUNITY_EXTENSIONS=true k6 x mytool
Disable automatic extension resolution
You can disable this feature by setting the environment variable K6_AUTO_EXTENSION_RESOLUTION to false:
K6_AUTO_EXTENSION_RESOLUTION=false k6 x mytoolBuild a custom k6 binary
To use subcommand extensions you’re developing or that aren’t available in the registry, build a custom k6 binary with xk6:
xk6 build --with xk6-subcommand-mytool=.This creates a k6 binary in your current directory that includes your extension.
After building, your subcommand is available under the k6 x namespace:
./k6 x mytoolTo see all available extension subcommands:
./k6 x helpConstructor requirements
The constructor function passed to RegisterExtension must:
- Accept a single
*state.GlobalStateparameter - Return a
*cobra.Command - Create a command whose
Usefield matches the registered extension name
Violating these requirements causes the extension to panic at startup, ensuring configuration errors are caught early.
Example: Complete validation tool
Here’s a more complete example that checks system requirements:
package validate
import (
"fmt"
"os"
"runtime"
"github.com/spf13/cobra"
"go.k6.io/k6/cmd/state"
"go.k6.io/k6/subcommand"
)
func init() {
subcommand.RegisterExtension("validate", newValidateCommand)
}
func newValidateCommand(gs *state.GlobalState) *cobra.Command {
cmd := &cobra.Command{
Use: "validate",
Short: "Verify system requirements",
Long: "Check if the system meets requirements for running tests",
RunE: func(cmd *cobra.Command, args []string) error {
gs.Logger.Info("Checking system requirements...")
// Check Go version
gs.Logger.Infof("Go version: %s", runtime.Version())
// Check available memory
var m runtime.MemStats
runtime.ReadMemStats(&m)
gs.Logger.Infof("Available memory: %d MB", m.Sys/1024/1024)
// Check environment variables
if broker := os.Getenv("MQTT_BROKER"); broker != "" {
gs.Logger.Infof("MQTT broker configured: %s", broker)
} else {
gs.Logger.Warn("MQTT_BROKER not set")
}
gs.Logger.Info("Validation complete")
return nil
},
}
return cmd
}Usage:
./k6 x validateAccess k6 runtime state
The GlobalState provides read-only access to k6’s configuration:
func newCommand(gs *state.GlobalState) *cobra.Command {
return &cobra.Command{
Use: "validate",
Short: "Validate k6 configuration",
Run: func(cmd *cobra.Command, args []string) {
// Access logger
gs.Logger.Info("Validating k6 configuration...")
// Access flags and options
if gs.Flags.Verbose {
gs.Logger.Debug("Verbose mode enabled")
}
// Access environment variables
gs.Logger.Infof("Working directory: %s", gs.Getwd)
},
}
}Add command flags
Use Cobra’s flag system to add options to your subcommand:
func newCommand(gs *state.GlobalState) *cobra.Command {
var target string
var verbose bool
cmd := &cobra.Command{
Use: "validate",
Short: "Validate configuration",
Run: func(cmd *cobra.Command, args []string) {
if verbose {
gs.Logger.Info("Verbose mode enabled")
}
gs.Logger.Infof("Validating target: %s", target)
// Validation logic here
},
}
cmd.Flags().StringVarP(&target, "target", "t", "localhost", "Target to validate")
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output")
return cmd
}Usage:
./k6 x validate --target example.com --verboseBest practices
- Read-only state: Never modify the
GlobalStatepassed to your constructor - Naming: Use descriptive, kebab-case names for your commands
- Documentation: Provide clear
ShortandLongdescriptions - Error handling: Return errors from
RunErather than panicking in command execution - Logging: Use
gs.Loggerfor consistent output with k6’s logging system


