Skip to main content

k6 TypeScript: Run .ts Load Tests Without a Build Step

k6 runs .ts files directly since v0.57, but it strips types without checking them. 11 TypeScript cases tested on k6 v2.3.0, and the decorators that hung it.

k6 TypeScript: Run .ts Load Tests Without a Build Step

k6 runs TypeScript test scripts directly: name the file test.ts and run k6 run test.ts, with no compiler, bundler or config file. Since k6 v0.57.0 (February 2025) any file ending in .ts is loaded through esbuild, which removes the type annotations and hands plain JavaScript to k6. What it does not do is check the types, so a script full of type errors runs anyway.

We ran 11 cases on k6 v2.3.0 to see where type stripping stops being enough. Typed code, local .ts modules, enums, namespaces and parameter properties all worked. A type error ran without a word. Imports without the .ts extension and tsconfig path aliases failed at start-up. And a single decorator made k6 v2.3.0 hang with no output at all; k6 inspect hung the same way on v1.7.1.

Key Takeaways

  • k6 v0.52.0 added .ts support as an experiment (June 2024), v0.57.0 made it automatic (February 2025), and v1.0.0 called it first-class (May 2025).
  • k6 strips types and never checks them. With @types/k6 installed, your editor flags type errors; in CI, run tsc --noEmit before k6 run.
  • Local imports need the full .ts extension, and a tsconfig path alias meant nothing to k6: both failed with exit code 107.
  • Decorators hung k6 before it printed anything in our runs, even under k6 inspect. Keep them out of test scripts.
  • Error stack traces point at the right line in your .ts file.

Does k6 support TypeScript?

Yes. k6 v2.3.0 loads any script or module whose name ends in .ts through esbuild’s loader, strips the types and runs the result, with no setup and no flag. Support arrived in three steps: an experimental compatibility mode in v0.52.0, automatic loading of .ts files in v0.57.0, and first-class status in the v1.0.0 release notes.

The v0.52.0 notes describe the trade-off that still applies: esbuild’s loader “removes the type information but doesn’t provide type safety”. The old experimental_enhanced compatibility mode is no longer needed. On v2.3.0 the mode is still accepted, with a warning:

CompatibilityMode "experimental_enhanced" is deprecated. Types are stripped by default for `.ts` files. Please move to using "base" instead as "experimental_enhanced" will be removed in the future

What works and what breaks in k6 TypeScript?

Typed code works, including the enums, namespaces and parameter properties that esbuild rewrites into JavaScript. Decorators, extensionless imports and alias paths do not. This is what each case did on k6 v2.3.0 (the typed test ran 5 users for 5 seconds; every other case one user and one iteration):

What we triedResult on k6 v2.3.0
Typed test importing a local module (./lib/api.ts)Ran, 93 of 93 checks passed
Assigning a string to a number variableRan; typeof printed string
Import without the extension (./lib/api)Exit 107: module couldn't be found on local disk
tsconfig paths alias (@lib/api.ts, with the paths entry in a tsconfig.json beside the script)Exit 107: couldn't be recognised as something k6 supports
enum and const enumRan (Level.High = 5, reverse lookup Level[5] = High)
namespace and constructor parameter propertiesRan
A method decoratorHung with no output; killed after 20 s
A class decoratorHung the same way; killed after 15 s
A .js script importing a .ts moduleRan
--compatibility-mode=base on a .ts scriptRan
An error thrown on line 5Stack trace pointed to line 5 (g-line.ts:5:8)

k6 inspect, which loads a script without running any iterations, also hung on the method-decorator script, and so did k6 inspect on the Homebrew build of k6 1.7.1. The same syntax in a .js file failed fast instead, with Unexpected token ILLEGAL and exit 107. If a shared helper library uses decorators, keep it out of the k6 import graph.

How do you write a k6 test in TypeScript?

Write the test as you would in JavaScript, add types, and save it as .ts. Import k6’s modules by their usual names (k6/http, k6/metrics), import your own modules with the .ts extension, and export options and a default function. k6 needs no tsconfig.json (that file is for your editor and tsc), and the @types/k6 package gives the editor the types for every k6 module.

This is the test we ran, with a small helper module:

// lib/api.ts
import http, { RefinedResponse, ResponseType } from 'k6/http';

export interface LoginBody { user: string; }
export const BASE: string = 'http://127.0.0.1:18089';

export function login(body: LoginBody): string {
  const r: RefinedResponse<ResponseType> = http.post(`${BASE}/login`, JSON.stringify(body), {
    headers: { 'Content-Type': 'application/json' },
  });
  return r.json('token') as string;
}
// test.ts
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Options } from 'k6/options';
import { login, BASE } from './lib/api.ts';

export const options: Options = {
  vus: 5,
  duration: '5s',
  thresholds: { http_req_failed: ['rate<0.05'] },
};

type Endpoint = 'fast' | 'slow';
const endpoints: Endpoint[] = ['fast', 'slow'];

export default function (): void {
  const token: string = login({ user: `u${__VU}` });
  const ep: Endpoint = endpoints[__ITER % endpoints.length];
  const res = http.get(`${BASE}/${ep}`, { headers: { Authorization: `Bearer ${token}` } });
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(0.2);
}

k6 run test.ts ran it for 5 seconds with 186 requests and 93 passing checks. The Options import deserves a note: k6/options exists only in the type definitions. Because Options is used only as a type, esbuild removes the import and k6 never looks for the module. When we imported k6/options as a value instead, k6 v2.3.0 logged Automatic extension resolution is enabled, tried to provision a custom binary for an unknown dependency, failed and exited 107. Writing import type { Options } from 'k6/options' makes the intent explicit.

Does k6 check types in .ts files?

No. k6 removes the types and runs whatever JavaScript is left, so a type error that tsc would reject goes straight to the load test. Our script assigned a string to a variable declared as number; k6 started, ran the iteration, and a log line inside it showed typeof returning string.

That makes type checking a separate step. Install @types/k6 (version 2.3.1, published 26 October 2026) as a dev dependency with typescript: your editor then flags type errors, and in CI you run tsc --noEmit before k6 run. The k6 step stays fast because nothing is compiled, and the check catches the mistakes k6 will not.

Why does my k6 TypeScript import fail?

Usually because the path is written the way a bundler or tsconfig would accept it. k6 resolves modules itself: a local import must name the file exactly, extension included (./lib/api.ts, not ./lib/api), and an alias import fails even when a tsconfig.json beside the script defines the alias. We ran @lib/api.ts with a paths entry for @lib/* in place. Both failed before the test started, with exit code 107.

The two messages tell the cases apart:

The moduleSpecifier "./lib/api" couldn't be found on local disk. Make sure that you've specified the right path to the file. [...]
The moduleSpecifier "@lib/api.ts" couldn't be recognised as something k6 supports.

Your editor may flag .ts extensions in imports. tsc accepts them when allowImportingTsExtensions is set with noEmit, which suits a k6 project where tsc only checks types. A .js test can import a .ts module too: ours did, and the call returned a token.

Can you run a .ts k6 test on LoadFocus?

Yes. LoadFocus accepts a .ts file as the k6 test script, as well as a .js one, along with data files your script reads with open(). Only one script file runs per test, so a helper such as ./lib/api.ts has to be bundled into the test script first. That is the one case where a build step comes back.

The guide to uploading k6 scripts lists the accepted files. The upload check requires an entry point: an export default function, or scenarios that name their functions. The guide to running a k6 load test covers that check and which plans can run from several locations at once.

Run tsc --noEmit before you upload, for the same reason as locally: k6 will not report type errors. Locations, user limits and plans are on the k6 load testing page.

Frequently Asked Questions

Do I need to compile TypeScript before running k6?

No. Since k6 v0.57.0, any script or module ending in .ts is stripped of its types by esbuild when k6 loads it, so k6 run test.ts works on its own. You only need the tsc compiler if you want type errors reported, which k6 itself never does.

Do I need @types/k6 to run a k6 test?

No. k6 runs .ts files without it. The @types/k6 package only gives your editor and the tsc compiler the types of k6’s modules, such as k6/http, so they can check the script before it runs. Install it as a dev dependency next to typescript.

Why does my k6 test hang with no output?

One cause we reproduced is a decorator in a .ts file. A method decorator hung k6 v2.3.0 under both k6 run and k6 inspect, and k6 inspect on v1.7.1 too; a class decorator hung k6 v2.3.0 run the same way. Nothing was printed until we killed the process. Remove decorators from the test script and from every file it imports.

Can k6 use tsconfig paths?

No. In our run, an alias import of @lib/api.ts failed with exit code 107 and the message that the module couldn’t be recognised as something k6 supports, even though a tsconfig.json beside the script defined that path. Use relative imports with the .ts extension instead.

What is the experimental_enhanced compatibility mode in k6?

It was the mode that made k6 strip types from .ts files through esbuild in k6 v0.52.0. Since v0.57.0, .ts files are handled automatically, and on k6 v2.3.0 the mode only prints a deprecation warning telling you to use base instead.

Sources

  • TypeScript support history: grafana/k6 release notes for v0.52.0 (25 June 2024), v0.57.0 (13 February 2025) and v1.0.0 (6 May 2025), github.com/grafana/k6/releases, read 6 November 2026.
  • @types/k6 2.3.1, published 26 October 2026: registry.npmjs.org/@types/k6, read 6 November 2026.
  • allowImportingTsExtensions and its noEmit requirement: TypeScript TSConfig reference, read 6 November 2026.
  • LoadFocus k6 file types, upload check and location rules: Uploading k6 Scripts and Data Files (loadfocus.com/docs/guides/k6-load-testing/uploading-k6-scripts), How to Run a New k6 Load Test (loadfocus.com/docs/guides/k6-load-testing/how-to-run-k6-load-test) and loadfocus.com/k6-load-testing, read 6 November 2026.
  • Test runs: k6 v2.3.0 (official macOS arm64 release, checksum-verified) and k6 v1.7.1 (Homebrew), against a local Node.js test API, on an Apple M1 Max, 6 November 2026.

Bogdan

Founder at LoadFocus

Bogdan builds and runs the tools this blog is about. He writes from what the products actually do in production, including the parts that break.

Weekly tips right in your inbox.

Don’t miss out on the latest testing tips and tricks, tools and news related to software testing.

Already +20,000 members in the list.

The Load Testing Platform with AI That Explains Your Results

Load test websites and APIs from 25 cloud regions, monitor page speed and uptime, and get AI analysis that explains your results in plain English.

Cloud load testing, website speed and API monitoring in one place.

Hi there! Can I share a cool thing I'm working on with you?

A software testing newsletter that sparks joy.

Our goal is to create helpful content for software developers and testers.

If you're not into it, you can unsubscribe instantly.