> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/grafana/k6/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Extensions

> Build custom k6 extensions in Go to add new functionality

Create custom k6 extensions to add JavaScript modules, output formats, secret sources, or CLI subcommands. Extensions are written in Go and compiled into k6 using xk6.

## Prerequisites

* Go 1.22 or later
* Understanding of Go programming
* Familiarity with k6 concepts
* Git for version control

## Extension Types

You can create four types of extensions:

1. **JavaScript Extensions**: Add custom JavaScript APIs
2. **Output Extensions**: Create custom result outputs
3. **Secret Source Extensions**: Implement secret providers
4. **Subcommand Extensions**: Add CLI subcommands

See [Extensions Overview](/extensions/overview) for details on each type.

## Project Setup

<Steps>
  ### Create Repository

  Create a new Go module for your extension:

  ```bash theme={null}
  mkdir xk6-myextension
  cd xk6-myextension
  go mod init github.com/yourname/xk6-myextension
  ```

  ### Add k6 Dependency

  ```bash theme={null}
  go get go.k6.io/k6@latest
  ```

  ### Create Main File

  Create your extension implementation:

  ```bash theme={null}
  touch myextension.go
  ```
</Steps>

<Note>
  Follow the `xk6-` naming convention for discoverability. Name your extension repository `xk6-extensionname`.
</Note>

## Creating a JavaScript Extension

JavaScript extensions add custom modules that can be imported in test scripts.

### Basic Structure

```go theme={null}
package myextension

import (
    "go.k6.io/k6/js/modules"
)

func init() {
    modules.Register("k6/x/myextension", New())
}

type RootModule struct{}
type ModuleInstance struct {
    vu modules.VU
}

func New() *RootModule {
    return &RootModule{}
}

func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &ModuleInstance{vu: vu}
}

func (mi *ModuleInstance) Exports() modules.Exports {
    return modules.Exports{Default: mi}
}
```

### Adding Functionality

Add methods that will be available in JavaScript:

```go theme={null}
func (mi *ModuleInstance) Greet(name string) string {
    return "Hello, " + name + "!"
}

func (mi *ModuleInstance) Add(a, b int) int {
    return a + b
}
```

Use in JavaScript:

```javascript theme={null}
import myext from 'k6/x/myextension';

export default function () {
  console.log(myext.greet('World'));
  console.log(myext.add(2, 3));
}
```

### Working with Metrics

Create custom metrics in your extension:

```go theme={null}
import (
    "go.k6.io/k6/js/modules"
    "go.k6.io/k6/metrics"
    "time"
)

type ModuleInstance struct {
    vu     modules.VU
    custom *metrics.Metric
}

func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &ModuleInstance{
        vu:     vu,
        custom: vu.InitEnv().Registry.MustNewMetric("custom_metric", metrics.Counter),
    }
}

func (mi *ModuleInstance) RecordValue(value float64) {
    state := mi.vu.State()
    ctx := mi.vu.Context()
    
    tags := state.Tags.GetCurrentValues().Tags
    metrics.PushIfNotDone(ctx, state.Samples, metrics.Sample{
        Time:       time.Now(),
        TimeSeries: metrics.TimeSeries{Metric: mi.custom, Tags: tags},
        Value:      value,
    })
}
```

<Warning>
  Always check if `vu.State()` is nil. It will be nil in the init context where metric emission is not allowed.
</Warning>

### Real Example

Here's a real JavaScript extension from k6's test suite (`xk6-js-test/jstest.go:1`):

```go theme={null}
package jstest

import (
    "fmt"
    "time"
    "go.k6.io/k6/js/modules"
    "go.k6.io/k6/metrics"
)

func init() {
    modules.Register("k6/x/jsexttest", New())
}

type (
    RootModule struct{}
    JSTest struct {
        vu   modules.VU
        foos *metrics.Metric
    }
)

func New() *RootModule {
    return &RootModule{}
}

func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &JSTest{
        vu:   vu,
        foos: vu.InitEnv().Registry.MustNewMetric("foos", metrics.Counter),
    }
}

func (j *JSTest) Exports() modules.Exports {
    return modules.Exports{Default: j}
}

func (j *JSTest) Foo(arg float64) (bool, error) {
    state := j.vu.State()
    if state == nil {
        return false, fmt.Errorf("the VU State is not available in the init context")
    }
    
    ctx := j.vu.Context()
    tags := state.Tags.GetCurrentValues().Tags.With("foo", "bar")
    metrics.PushIfNotDone(ctx, state.Samples, metrics.Sample{
        Time:       time.Now(),
        TimeSeries: metrics.TimeSeries{Metric: j.foos, Tags: tags},
        Value:      arg,
    })
    
    return true, nil
}
```

## Creating an Output Extension

Output extensions send test results to custom backends.

### Basic Implementation

See [Creating Custom Outputs](/results/custom-outputs) for a comprehensive guide.

Quick example:

```go theme={null}
package myoutput

import (
    "go.k6.io/k6/output"
    "go.k6.io/k6/metrics"
)

func init() {
    output.RegisterExtension("myoutput", New)
}

type Output struct {
    output.SampleBuffer
    params output.Params
}

func New(params output.Params) (output.Output, error) {
    return &Output{params: params}, nil
}

func (o *Output) Description() string {
    return "my-custom-output"
}

func (o *Output) Start() error {
    return nil
}

func (o *Output) Stop() error {
    samples := o.GetBufferedSamples()
    // Process samples
    return nil
}
```

## Creating a Secret Source Extension

Secret source extensions provide secure configuration values.

### Implementation

```go theme={null}
package mysecrets

import (
    "go.k6.io/k6/ext"
)

func init() {
    ext.Register("mysecrets", ext.SecretSourceExtension, &SecretSource{})
}

type SecretSource struct{}

func (s *SecretSource) GetSecret(key string) (string, error) {
    // Fetch secret from your backend
    return fetchSecretFromVault(key)
}
```

## Creating a Subcommand Extension

Subcommand extensions add new CLI commands to k6.

### Implementation

```go theme={null}
package mycommand

import (
    "github.com/spf13/cobra"
    "go.k6.io/k6/ext"
)

func init() {
    ext.Register("mycommand", ext.SubcommandExtension, NewCommand)
}

func NewCommand() *cobra.Command {
    return &cobra.Command{
        Use:   "mycommand",
        Short: "My custom command",
        RunE: func(cmd *cobra.Command, args []string) error {
            // Command implementation
            return nil
        },
    }
}
```

Use it:

```bash theme={null}
k6 mycommand [args]
```

## Extension Registration

All extensions must register in an `init()` function using `ext.Register()` (`ext/ext.go:62`):

```go theme={null}
import "go.k6.io/k6/ext"

func init() {
    ext.Register(
        "extensionname",           // Name
        ext.JSExtension,            // Type
        New(),                      // Module/implementation
    )
}
```

<Note>
  Registration happens automatically when the extension package is imported during build.
</Note>

## Testing Your Extension

### Unit Tests

```go theme={null}
package myextension

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestGreet(t *testing.T) {
    mi := &ModuleInstance{}
    result := mi.Greet("World")
    assert.Equal(t, "Hello, World!", result)
}
```

### Integration Tests

Create a test script:

```javascript theme={null}
// test.js
import myext from 'k6/x/myextension';
import { check } from 'k6';

export default function () {
  const result = myext.greet('k6');
  check(result, {
    'greeting works': (r) => r === 'Hello, k6!',
  });
}
```

Build and test:

```bash theme={null}
xk6 build --with github.com/yourname/xk6-myextension=.
./k6 run test.js
```

## Building and Using

### Local Development

Build k6 with your local extension:

```bash theme={null}
xk6 build --with github.com/yourname/xk6-myextension=.
```

### Publishing

<Steps>
  ### Push to GitHub

  ```bash theme={null}
  git add .
  git commit -m "Initial extension implementation"
  git tag v0.1.0
  git push origin main --tags
  ```

  ### Use Published Extension

  Others can now use:

  ```bash theme={null}
  xk6 build --with github.com/yourname/xk6-myextension@v0.1.0
  ```
</Steps>

## Best Practices

<Steps>
  ### Follow Go Conventions

  Use standard Go project layout and naming conventions.

  ### Document Your API

  Provide clear documentation for all exported functions:

  ```go theme={null}
  // Greet returns a greeting message for the given name.
  func (mi *ModuleInstance) Greet(name string) string {
      return "Hello, " + name + "!"
  }
  ```

  ### Handle Errors Properly

  Return errors to JavaScript instead of panicking:

  ```go theme={null}
  func (mi *ModuleInstance) DoSomething() error {
      if err := operation(); err != nil {
          return fmt.Errorf("operation failed: %w", err)
      }
      return nil
  }
  ```

  ### Validate Inputs

  Check all inputs from JavaScript:

  ```go theme={null}
  func (mi *ModuleInstance) SetValue(val int) error {
      if val < 0 {
          return errors.New("value must be non-negative")
      }
      // ...
  }
  ```

  ### Use Context Appropriately

  Respect cancellation:

  ```go theme={null}
  func (mi *ModuleInstance) LongOperation() error {
      ctx := mi.vu.Context()
      select {
      case <-ctx.Done():
          return ctx.Err()
      case result := <-doWork():
          return nil
      }
  }
  ```

  ### Avoid Blocking

  Don't block VU execution with long operations:

  ```go theme={null}
  // Bad: blocks VU
  func (mi *ModuleInstance) SlowSync() {
      time.Sleep(5 * time.Second)
  }

  // Good: async or fast
  func (mi *ModuleInstance) FastAsync() chan Result {
      ch := make(chan Result, 1)
      go func() {
          // Long operation
          ch <- result
      }()
      return ch
  }
  ```
</Steps>

## Common Patterns

### Connection Pooling

```go theme={null}
type ModuleInstance struct {
    vu   modules.VU
    pool *ConnectionPool
}

func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    return &ModuleInstance{
        vu:   vu,
        pool: getOrCreatePool(vu),
    }
}
```

### Configuration

```go theme={null}
type Config struct {
    Host string `json:"host"`
    Port int    `json:"port"`
}

func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
    var config Config
    if err := json.Unmarshal(vu.InitEnv().Options, &config); err != nil {
        // Handle error
    }
    return &ModuleInstance{config: config}
}
```

### Resource Cleanup

```go theme={null}
func (mi *ModuleInstance) Close() error {
    if mi.connection != nil {
        return mi.connection.Close()
    }
    return nil
}
```

## Debugging

### Enable Verbose Logging

```go theme={null}
import "github.com/sirupsen/logrus"

func (mi *ModuleInstance) Debug(msg string) {
    logger := mi.vu.InitEnv().Logger
    logger.WithField("extension", "myext").Debug(msg)
}
```

### Use k6 Verbose Mode

```bash theme={null}
./k6 run --verbose test.js
```

## Example Extensions

Study these official examples:

* **xk6-redis**: [https://github.com/grafana/xk6-redis](https://github.com/grafana/xk6-redis)
* **xk6-kafka**: [https://github.com/mostafa/xk6-kafka](https://github.com/mostafa/xk6-kafka)
* **xk6-sql**: [https://github.com/grafana/xk6-sql](https://github.com/grafana/xk6-sql)
* **xk6-browser**: [https://github.com/grafana/xk6-browser](https://github.com/grafana/xk6-browser)

## Resources

* [k6 Extension Registry](https://k6.io/docs/extensions/)
* [xk6 Documentation](https://github.com/grafana/xk6)
* [k6 API Documentation](https://pkg.go.dev/go.k6.io/k6)
* [Community Forum](https://community.grafana.com/c/grafana-k6/)

## Next Steps

* [Using Extensions](/extensions/using-extensions) - Learn to use extensions
* [Extensions Overview](/extensions/overview) - Understand extension types
* [Creating Custom Outputs](/results/custom-outputs) - Specialized output guide
