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
- Move all `new Counter/Gauge/Trend/Rate` declarations to the top level of the script, outside any exported function
- If the metric name is computed, compute it at the top level too and keep the constructor there
- 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
- Declare every Counter/Gauge/Trend/Rate at the top level of the script file
- Review refactors: extracting top-level code into functions called from default() moves construction out of init context
- Remember the mirror rule — .add() belongs inside VU code, construction belongs in init
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
- Unexpected end of selector while parsing selector `${selecto
- exported 'setup' must be a function
- exported 'teardown' must be a function
- no gRPC connection, you must call connect first
- {msg}
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/c40d8ae04e257bd0.
Report an issue: GitHub.