Skip to main content
The measurements option accepts an ordered array of measurement 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';

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

Default measurement sequence

When no measurements option is provided, the engine runs the following sequence. It follows a ramp-up strategy: file sizes grow progressively so the engine can characterize bandwidth across a range of connection speeds.
[
  { type: 'latency', numPackets: 1 },                  // initial TTFB estimation
  { type: 'download', bytes: 1e5, count: 1, bypassMinDuration: true }, // initial download estimation
  { 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 engine applies a ramp-up optimization for download and upload measurements. Once a request in a given direction takes at least bandwidthFinishRequestDuration milliseconds (default: 1000 ms), all subsequent measurement sets for that direction are skipped. This means slower connections naturally stop at smaller file sizes, while faster connections continue to larger ones. You can disable early stopping by setting bandwidthFinishRequestDuration to 0.

Measurement types

latency

Measures unloaded (idle) round-trip latency. Each packet is a GET request to the download API 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
The number of GET requests to perform. More packets yield a more stable latency estimate. The default sequence uses 1 for an initial quick estimate and 20 for the main measurement.
Example
{ 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 for each request is calculated as transferSize (bits) / duration (s), where duration excludes server processing time.
type
"download"
required
Must be "download".
bytes
number
required
The number of bytes to request. Passed to the download API as a query parameter. Use scientific notation for readability (e.g., 1e6 for 1 MB, 1e8 for 100 MB).
count
number
required
The number of sequential requests to perform at this file size. Multiple requests improve statistical stability at any given size tier.
bypassMinDuration
boolean
default:"false"
When true, the bandwidthMinRequestDuration threshold check is ignored for this measurement set and the engine always proceeds to the next set regardless of how long requests took. Used for the very first download entry in the default sequence to establish an initial estimate on all connection speeds.
Example
{ 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 mirrors the download type.
type
"upload"
required
Must be "upload".
bytes
number
required
The number of bytes to post in each request body.
count
number
required
The number of sequential requests to perform at this file size.
bypassMinDuration
boolean
default:"false"
When true, ignores the bandwidthMinRequestDuration check and always continues to the next measurement set.
Example
{ 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 can be sent in batches with configurable inter-batch delays to avoid flooding the connection.
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 greater accuracy.
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.
batchSize
number
default:"10"
Number of packets per batch. If batchSize is larger than numPackets, all packets are sent in a single batch.
batchWaitTime
number
default:"10"
Time in milliseconds to wait between consecutive batches.
connectionTimeout
number
default:"5000"
Time in milliseconds to wait when establishing the connection to the TURN server before giving up.
Example
{
  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 upload/download 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,
    },
  ],
});

Build docs developers (and LLMs) love