---
title: "Subcommand extensions | Grafana k6 documentation"
description: "Follow these steps to build a subcommand extension for k6."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# 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:

- [Go](https://go.dev/doc/install)
- [Git](https://git-scm.com/install/)

You also need to install xk6:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
go install go.k6.io/xk6/cmd/xk6@latest
```

## Write a simple extension

1. Set up a directory to work in.
   
   Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```bash
   mkdir xk6-subcommand-mytool; cd xk6-subcommand-mytool; go mod init xk6-subcommand-mytool
   ```
2. The core of a subcommand extension is a constructor function that creates a Cobra command. The constructor receives k6’s `GlobalState` for read-only access to runtime configuration.
   
   Create an example command named `mytool`:
   
   Go ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
   
   ```go
   package mytool
   
   import (
       "github.com/spf13/cobra"
       "go.k6.io/k6/v2/cmd/state"
       "go.k6.io/k6/v2/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
           },
       }
   }
   ```
3. The extension uses the `subcommand.RegisterExtension` function to register itself during initialization. The first argument is the command name (which must match the command’s `Use` field), and the second is the constructor function.

> Caution
> 
> The `GlobalState` provided 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](/docs/k6/v2.3.x/extensions/explore/), you can use it directly without building a custom binary. k6 automatically detects, builds, and loads the extension when you invoke it:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
k6 x mytool
```

This 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_EXTENSIONS` set to `true`.
> 
> Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy
> 
> ```bash
> K6_ENABLE_COMMUNITY_EXTENSIONS=true k6 x mytool
> ```

For real-world examples of official subcommand extensions that ship through this exact path, see [the k6 MCP server](/docs/k6/v2.3.x/set-up/configure-ai-assistant/) (exposed as `k6 x mcp`) and [`k6 x agent`](/docs/k6/v2.3.x/set-up/configure-ai-assistant/bootstrap-with-k6-x-agent/), which scaffolds AI editor integrations on top of it.

### Disable automatic extension resolution

You can disable this feature by setting the environment variable `K6_AUTO_EXTENSION_RESOLUTION` to `false`:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
K6_AUTO_EXTENSION_RESOLUTION=false k6 x mytool
```

## Discover available subcommands

Running `k6 x` without arguments lists the available subcommands, both the ones built into the binary and those advertised by the [extension catalog](/docs/k6/v2.3.x/extensions/explore/) (official and community):

text ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```text
$ k6 x
...
Available Commands:
  agent       Bootstrap an AI-assisted k6 testing workflow in any editor
  docs        CLI k6 docs for AI agents and users
  explore     Explore k6 extensions for Automatic Resolution
  mcp         An MCP server for k6 for AI agents
```

Shell tab-completion surfaces the same set once the catalog has been cached locally by a prior `k6 x` run, so completion never blocks on the network. This makes a k6 binary self-describing, which is particularly useful for AI agents driving k6.

## Build 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](https://github.com/grafana/xk6):

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
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:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
./k6 x mytool
```

To see all available extension subcommands:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
./k6 x help
```

## Constructor requirements

The constructor function passed to `RegisterExtension` must:

1. Accept a single `*state.GlobalState` parameter
2. Return a `*cobra.Command`
3. Create a command whose `Use` field 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:

Go ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```go
package validate

import (
    "fmt"
    "os"
    "runtime"

    "github.com/spf13/cobra"
    "go.k6.io/k6/v2/cmd/state"
    "go.k6.io/k6/v2/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:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
./k6 x validate
```

## Access k6 runtime state

The `GlobalState` provides read-only access to k6’s configuration:

Go ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```go
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:

Go ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```go
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:

Bash ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```bash
./k6 x validate --target example.com --verbose
```

## Best practices

- **Read-only state**: Never modify the `GlobalState` passed to your constructor
- **Naming**: Use descriptive, kebab-case names for your commands
- **Documentation**: Provide clear `Short` and `Long` descriptions
- **Error handling**: Return errors from `RunE` rather than panicking in command execution
- **Logging**: Use `gs.Logger` for consistent output with k6’s logging system
