Skip to main content

Overview

The measurements config option accepts an ordered array of MeasurementConfig objects. The engine executes each entry in sequence. Every object must include a type field; the remaining fields depend on the type.
import SpeedTest from '@cloudflare/speedtest';
import type { MeasurementConfig } from '@cloudflare/speedtest';

const engine = new SpeedTest({
  measurements: [
    { type: 'latency', numPackets: 20 },
    { type: 'download', bytes: 1e6, count: 10 },
    { type: 'upload',   bytes: 1e6, count: 10 },
    { type: 'packetLoss' },
  ],
});

TypeScript union type

type MeasurementConfig = {
  type: 'latency';
  numPackets: number;
} | {
  type: 'download' | 'upload';
  bytes: number;
  count: number;
  bypassMinDuration?: boolean;
} | {
  type: 'packetLoss';
  numPackets?: number;
  batchSize?: number;
  batchWaitTime?: number;
  responsesWaitTime?: number;
  connectionTimeout?: number;
};

Default measurement sequence

When no measurements option is provided, the engine runs the following sequence. File sizes grow progressively so the engine can characterize bandwidth across a wide range of connection speeds — a strategy called ramp-up:
[
  { type: 'latency', numPackets: 1 },                                    // initial TTFB estimate
  { type: 'download', bytes: 1e5, count: 1, bypassMinDuration: true },  // initial download estimate
  { type: 'latency', numPackets: 20 },
  { type: 'download', bytes: 1e5, count: 9 },
  { type: 'download', bytes: 1e6, count: 8 },
  { type: 'upload',   bytes: 1e5, count: 8 },
  {
    type: 'packetLoss',
    numPackets: 1e3,
    batchSize: 10,
    batchWaitTime: 10,       // ms between batches
    responsesWaitTime: 3000  // ms to wait after last received packet
  },
  { type: 'upload',   bytes: 1e6,   count: 6 },
  { type: 'download', bytes: 1e7,   count: 6 },
  { type: 'upload',   bytes: 1e7,   count: 4 },
  { type: 'download', bytes: 2.5e7, count: 4 },
  { type: 'upload',   bytes: 2.5e7, count: 4 },
  { type: 'download', bytes: 1e8,   count: 3 },
  { type: 'upload',   bytes: 5e7,   count: 3 },
  { type: 'download', bytes: 2.5e8, count: 2 },
]
The ramp-up strategy means slower connections naturally stop at smaller file sizes: once a request in a given direction takes at least bandwidthFinishRequestDuration milliseconds (default: 1000 ms), all remaining measurement sets for that direction are skipped. Set bandwidthFinishRequestDuration to 0 to disable early stopping and always run every configured step.

Measurement variants

latency

Measures unloaded (idle) round-trip latency. Each packet is a GET request to the download API endpoint with bytes=0. The round-trip time is extracted from the requestStartresponseStart timing interval provided by the PerformanceResourceTiming API.
type
"latency"
required
Must be "latency".
numPackets
number
required
Number of GET requests to perform. More packets yield a more stable latency estimate. The default sequence uses 1 for a quick initial estimate and 20 for the main measurement.
{ type: 'latency', numPackets: 20 }

download

Performs a set of download GET requests against downloadApiUrl. Each request fetches a response body of exactly bytes bytes. Bandwidth per request is calculated as transferSize (bits) / duration (seconds), where duration excludes server processing time.
type
"download"
required
Must be "download".
bytes
number
required
Number of bytes to request. Passed to the download API as a query parameter. Use scientific notation for readability (for example, 1e6 for 1 MB, 1e8 for 100 MB).
count
number
required
Number of sequential requests to perform at this file size. Multiple requests improve statistical stability at each size tier.
bypassMinDuration
boolean
default:"false"
When true, the bandwidthFinishRequestDuration threshold check is skipped for this entry and the engine always proceeds to the next measurement set regardless of request duration. Used in the default sequence for the very first download entry to ensure an initial estimate is always obtained, even on very slow connections.
{ type: 'download', bytes: 1e7, count: 6 }

upload

Performs a set of upload POST requests against uploadApiUrl. Each request posts a body of exactly bytes bytes. Bandwidth calculation is identical to the download type.
type
"upload"
required
Must be "upload".
bytes
number
required
Number of bytes to include in each POST request body.
count
number
required
Number of sequential requests to perform at this payload size.
bypassMinDuration
boolean
default:"false"
When true, ignores the bandwidthFinishRequestDuration check and always proceeds to the next measurement set.
{ type: 'upload', bytes: 2.5e7, count: 4 }

packetLoss

Measures packet loss by sending UDP packets to a WebRTC TURN server in a round-trip fashion, then counting how many packets were not returned. Packets are sent in configurable batches to avoid flooding the connection. All fields are optional — omitting { type: 'packetLoss' } entirely uses the defaults shown below.
You must provide your own TURN server to obtain packet loss results. The public Cloudflare TURN server is deprecated. Configure it via turnServerUri plus either turnServerCredsApiUrl or the turnServerUser / turnServerPass pair.
type
"packetLoss"
required
Must be "packetLoss".
numPackets
number
default:"100"
Total number of UDP packets to send. The default sequence overrides this to 1000 for higher accuracy.
batchSize
number
default:"10"
Number of packets per batch. If batchSize is greater than numPackets, all packets are sent in a single batch.
batchWaitTime
number
default:"10"
Time in milliseconds to wait between consecutive batches.
responsesWaitTime
number
default:"5000"
Time in milliseconds to wait after the last received packet before declaring the measurement complete. Any packets not returned within this window are counted as lost. The default sequence uses 3000 ms.
connectionTimeout
number
default:"5000"
Time in milliseconds to wait when establishing the connection to the TURN server before giving up.
{
  type: 'packetLoss',
  numPackets: 1000,
  batchSize: 10,
  batchWaitTime: 10,
  responsesWaitTime: 3000,
}

Custom measurement sequences

Latency-only test

const engine = new SpeedTest({
  measurements: [
    { type: 'latency', numPackets: 50 },
  ],
});

Download-only test with fixed file sizes

const engine = new SpeedTest({
  measurements: [
    { type: 'latency', numPackets: 5 },
    { type: 'download', bytes: 1e5, count: 1, bypassMinDuration: true },
    { type: 'download', bytes: 1e6, count: 5 },
    { type: 'download', bytes: 1e7, count: 5 },
    { type: 'download', bytes: 1e8, count: 3 },
  ],
});

Symmetric download and upload with packet loss

const engine = new SpeedTest({
  measurements: [
    { type: 'latency', numPackets: 20 },
    { type: 'download', bytes: 1e5, count: 1, bypassMinDuration: true },
    { type: 'download', bytes: 1e6, count: 5 },
    { type: 'download', bytes: 1e7, count: 5 },
    { type: 'upload',   bytes: 1e6, count: 5 },
    { type: 'upload',   bytes: 1e7, count: 5 },
    {
      type: 'packetLoss',
      numPackets: 500,
      batchSize: 10,
      batchWaitTime: 10,
      responsesWaitTime: 3000,
    },
  ],
});
When building a minimal latency-only or download-only test, set autoStart: false and assign onFinish before calling engine.play() to avoid missing early results.

Build docs developers (and LLMs) love