skip to content

In k6, which network imports are accepted, and how is a fetched module handled?

level: seniorimportance: should knowfreq 44%

answer

  1. two schemes only
  2. plain http is refused outright
  3. the URL is the version pin
  4. remote code runs as your script
  5. archive freezes the fetched bytes

basics

~20 s

Only https is accepted; k6 refuses http and every other scheme for imports. It downloads the module at load time, executes it as your test's own code, resolves that module's relative imports against its https origin, and forbids it from reaching local files.

solid answer

~40 s

k6 supports exactly two schemes for module specifiers, `file` and `https`. An `http://` or `ws://` import is refused with `only supported schemes for imports are file and https`. A remote module such as `https://jslib.k6.io/k6-utils/1.5.0/index.js` is fetched while the script is loading, cached under the run's https filesystem, and then executed with the same privileges as your own script — so the version in the URL is the only pin you get, and trusting the host is a real decision. Anything that module imports relatively is fetched from the same origin, and it may not import a `file://` path at all: k6 rejects that with `origin (…) not allowed to load local file`. `k6 archive` freezes the fetched bytes into a tar so a later run is identical.

code

javascript · 5 lines
javascript
import { randomItem } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';

export default function () {
  randomItem([1, 2, 3]);
}

go deeper

for a junior

Know that k6 can import straight from an https URL, that jslib is the library Grafana publishes that way, and that the version number is part of the URL you write.

for a middle

Explain that only file and https are accepted, that the fetch happens once while the script loads, and that a non-200 response fails the run before any virtual user starts.

for a senior

Show that you treat a remote import as executing third-party code inside your test, that you pin the version in the URL, and that you reach for k6 archive or a vendored copy when a run has to be reproducible or offline.

for a principal

The call is whether the organisation accepts fetching executable code at test time at all. Weigh the convenience of one shared URL against supply-chain exposure, egress rules on CI runners, and the audit story an archived tar gives you that a live fetch does not.

## Which network specifiers k6 accepts k6's loader treats any specifier containing `://` as a URL and then checks the scheme against a list of exactly two entries: - `https` — fetched over the network; - `file` — read from the local filesystem, equivalent to an absolute path. Everything else is refused before a request is made. `import x from 'http://example.com/lib.js'` fails with `only supported schemes for imports are file and https, http://example.com/lib.js has \`http\``, and a `ws://` specifier fails the same way. Plain HTTP is not a fallback and there is no flag to permit it, so a helper published on an internal host has to be served over TLS to be importable. Two historical shorthands are also gone. Specifiers beginning `cdnjs.com/…` or `github.com/…` — which older k6 material still shows — are rejected with a message telling you that these special URLs are no longer supported and to paste the real URL to the actual JavaScript file instead. ## What a remote import actually does The canonical example is Grafana's own jslib: ```javascript import { randomItem } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js'; ``` At load time, before any virtual user exists, k6 issues a GET for that URL with `_k6=1` appended to the query string, and retries once without the parameter if the first attempt fails, because some hosts behave oddly with unknown query arguments. A non-200 response is an error — a 404 surfaces as `not found: <url>`, anything else as `wrong status code (<n>) for: <url>`. The fetch has a one-minute timeout. On success the bytes are written into the run's `https` filesystem, so the same URL is not fetched twice within a run. The downloaded source is then compiled and executed as part of your test. The k6 documentation states this plainly: remote modules are downloaded and executed at runtime, which makes it extremely important to trust the code before including it in a test script. ## What a remote module may and may not reach | From a module loaded over https | Allowed? | |---|---| | A relative specifier such as `./util.js` | Yes — fetched from the same https origin | | Another absolute `https://` URL | Yes | | A built-in module such as `k6/http` | Yes — served from the registry, not the network | | A local file via `file:///etc/shadow` or an absolute path | No | The last row is a deliberate boundary, not an accident. k6 refuses it with `origin (https://example.com/) not allowed to load local file: file:///etc/shadow`. A remote library therefore cannot quietly read something off the machine running the test through the module loader. ## Versioning, because there is no lockfile k6 has no manifest and no lockfile for JavaScript modules. That leaves the URL itself as the entire pinning mechanism, which is why jslib publishes under versioned paths such as `/k6-utils/1.5.0/index.js`. Three consequences follow: 1. **Never import an unversioned or "latest" URL.** The next run silently becomes a different test, and nothing in the output tells you the library changed. 2. **A URL is a runtime dependency of every run.** If the host is unreachable, or your CI runners have no egress, the load fails before the test starts. 3. **The host is trusted with code execution.** A compromised or redirected asset runs inside your test with the same reach as your own script. ## Freezing the graph with an archive `k6 archive` resolves the whole module graph once and writes it to a tar: ``` metadata.json options, environment, k6 version data the main script file/… every local module that was imported https/… every remote module that was fetched ``` `k6 run archive.tar` then executes from those stored bytes, so the run does not touch the network for modules at all. That turns a remote import from a live dependency into a build artefact you can review, diff and hand to another machine — the archive command's own help calls it a fully self-contained test run that can be executed identically elsewhere. ## Practical guidance - Pin every remote import to an exact version in the URL and treat a version bump as a reviewed change. - If a run must be reproducible or offline, archive it, or download the library and import it as a local file with its extension written out. - Remember that the official k6 Docker image contains none of your files: remote imports work out of the box in a container, local ones need the directory mounted.

  • Why can a helper served over plain http not be imported into a k6 script?
    Because k6's loader accepts only the `file` and `https` schemes and rejects anything else before making a request, reporting `only supported schemes for imports are file and https`. There is no override flag. An internal library therefore has to be served over TLS, committed into the repository as a local file, or baked into an archive.
  • What stops a remote k6 module from reading a file off the machine running the test?
    The loader refuses to resolve a `file://` specifier when the importing module's own origin is https, reporting `origin (…) not allowed to load local file: …`. The rule is applied during resolution, so the read never happens. It constrains the module loader only, which is one more reason to trust a remote library before importing it.
  • Does k6 refetch a remote module for every virtual user?
    No. The module graph is resolved and fetched once while the test is loading, and the downloaded source is stored in the run's https filesystem, so the same URL is not requested again during that run. `k6 archive` goes further and stores those bytes on disk so that a later `k6 run archive.tar` needs no network at all.

saying these in an interview costs you the question

  • any http URL can be imported if it returns JavaScript
  • remote modules are sandboxed from the rest of the script
  • importing from github.com/user/repo still works
  • each virtual user refetches the remote module
  • an unversioned CDN URL is fine, it rarely changes