grafana/k6 · error

metrics must be declared in the init context

Error message

metrics must be declared in the init context

What it means

Custom metric constructors (new Counter, new Gauge, new Trend, new Rate from k6/metrics) must run in the init context — the top-level script phase where VUs and their metric registries are set up. newMetric (internal/js/modules/k6/metrics/metrics.go:30) checks mi.vu.InitEnv(); when nil (no init environment), it throws "metrics must be declared in the init context". Metric objects must exist once per VU before iterations start, not be created mid-iteration.

Source

Thrown at internal/js/modules/k6/metrics/metrics.go:31

	"go.k6.io/k6/v2/js/common"
	"go.k6.io/k6/v2/js/modules"
	"go.k6.io/k6/v2/metrics"
)

// Metric is a wrapper around custom metrics
type Metric struct {
	metric *metrics.Metric
	vu     modules.VU
}

// ErrMetricsAddInInitContext is error returned when adding to metric is done in the init context
var ErrMetricsAddInInitContext = common.NewInitContextError("Adding to metrics in the init context is not supported")

func (mi *ModuleInstance) newMetric(call sobek.ConstructorCall, t metrics.MetricType) (*sobek.Object, error) {
	initEnv := mi.vu.InitEnv()
	if initEnv == nil {
		return nil, errors.New("metrics must be declared in the init context")
	}
	rt := mi.vu.Runtime()
	c, _ := sobek.AssertFunction(rt.ToValue(func(name string, isTime ...bool) (*sobek.Object, error) {
		valueType := metrics.Default
		if len(isTime) > 0 && isTime[0] {
			valueType = metrics.Time
		}
		m, err := initEnv.Registry.NewMetric(name, t, valueType)
		if err != nil {
			return nil, err
		}
		metric := &Metric{metric: m, vu: mi.vu}
		o := rt.NewObject()
		err = o.DefineDataProperty("name", rt.ToValue(name), sobek.FLAG_FALSE, sobek.FLAG_FALSE, sobek.FLAG_TRUE)
		if err != nil {
			return nil, err
		}
		if err = o.Set("add", rt.ToValue(metric.add)); err != nil {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Move all `new Counter/Gauge/Trend/Rate` declarations to the top level of the script, outside any exported function
  2. If the metric name is computed, compute it at the top level too and keep the constructor there
  3. Keep only `.add()` calls inside default/setup/teardown

Example fix

// before
export default function () {
  const errors = new Counter('app_errors');
  errors.add(1);
}

// after
const errors = new Counter('app_errors');
export default function () {
  errors.add(1);
}
Defensive patterns

Strategy: validation

Validate before calling

// Structural rule, not a runtime API: declare metrics at module top level
const requestErrors = new Counter('request_errors');
const latency = new Trend('custom_latency', true);
// only use requestErrors.add / latency.add inside functions

Prevention

When it happens

Trigger: Calling `new Counter('my_counter')` (or Gauge/Trend/Rate) inside the exported default function, setup(), teardown(), or any function invoked during those phases.

Common situations: Growing scripts that start creating metrics inside handlers; refactoring top-level code into functions that are called from default(); copy-pasting helper code that constructs metrics lazily; this is distinct from the sibling error for metric.add() in init context.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/c40d8ae04e257bd0. Report an issue: GitHub.