#6334·k6

A k6 extension can only be instantiated if the test script imports it, with no way to guarantee that independent of script content

Author: memCreated Aug 25, 2026Updated Sep 14, 2026
Labelsfeaturetriage

Feature Description

A k6 Go extension (a JS module registered via modules.Register) only gets instantiated, i.e. Module.NewModuleInstance(vu) Instance gets called, as a side effect of the test script containing import "k6/x/foo" (or the CommonJS equivalent). There is no way for whoever runs k6 run to guarantee an extension is active for every VU independent of what the script itself contains. If the script doesn't import the extension, theextension's Go code never runs at all: it isn't merely inactive, it's never constructed, so it has no way to observe that it was left out.

Background

Registration and instantiation are two separate steps, and only the first one is unconditional:

  • modules.Register(name, mod) runs from the extension's own package init(), so the extension's root value exists in the process as soon as the binary starts, regardless of what test is being run.
  • Module.NewModuleInstance(vu) Instance is the per-VU construction step, and it is only ever called as part of resolving the script's own module graph: when the script (or something it imports) references the extension's registered name, k6's module resolver links that import, and sobek calls NewModuleInstance while evaluating the module graph. If nothing in the script's import graph names the extension, that code path is never reached, for any VU, for the whole test run.

The only things that get set up per VU independent of any import are a fixed, hardcoded set of built-ins (timers, WebCrypto, text encoding). This isn't a mechanism extension authors can opt into; it's internal to k6's own runtime setup.

Concrete problem this causes

Some extensions need Go-side setup on every VU regardless of what the test script looks like, for example a usage report extension, reporting anonymized data about k6 runs needs its Go code constructed for each VU whether or not the script author thought to reference it.

Today the only way to make that happen is to require the script to import the extension, even if the script never calls anything from it. This is a workable trigger, but it ties something that's really a deployment/runtime concern (should this Go-side behavior be active for this test run?) to the contents of the test script:

  • Whether the extension's Go-side behavior is active depends on whether a particular line exists in the script, not on any configuration passed to k6 run. Enabling or disabling it means editing every script that should or shouldn't have it, rather than flipping something at the level the test is invoked from.
  • If the import is missing — forgotten, removed during a refactor, or never added because a given script was written without knowledge of the extension — the extension is silently absent. There's no error, no warning, nothing to distinguish "intentionally not using this extension" from "meant to have it but it's not there."
  • A side-effect-only import (import "k6/x/foo"; with nothing used from it) is a foreign idiom to justify to script authors: it exists purely to satisfy k6's instantiation mechanism, not because the script has any use for what the extension exports.

The hypothetical usage report extension would have to be imported by the user, which might be seen as a good thing. A different mechanism would be to set an environment variable or configuration file opting-in into the report. If the user opts in, with the current APIs it's not possible for the extension to do this.

Scope

The usage report extension is the simplest example that exemplifies this, but there are other applications of this: for example an extension that needs to make a connection to a service, regardless of the script, an extension that needs to instrument HTTP calls, regardless of the script (e.g. to inject event collection using net/http/trace).

This has no interaction with the JS-facing API (which the extension might also expose for other reasons). This is only about what causes the extension to be instantiated.

Suggested Solution (optional)

No response

Already existing or connected issues / PRs (optional)

No response