Skip to main content
The measurements config option accepts an array of measurement descriptors that control exactly what the engine measures and in what order. Each descriptor requires a type field plus type-specific options.

Latency-only test

Skip all bandwidth measurements and run only latency probes:
import SpeedTest from '@cloudflare/speedtest';

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

engine.onFinish = results => {
  console.log('Latency:', results.getUnloadedLatency(), 'ms');
  console.log('Jitter:', results.getUnloadedJitter(), 'ms');
  console.log('All latency points:', results.getUnloadedLatencyPoints());
};
Increasing numPackets gives more data points for a more accurate latency and jitter estimate, at the cost of a longer test.

Quick bandwidth test

For a fast estimate, use a single small-file download and upload pass with bypassMinDuration: true to skip the normal ramp-up gate:
import SpeedTest from '@cloudflare/speedtest';

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

engine.onFinish = results => {
  const { download, upload, latency } = results.getSummary();
  console.log(`Download: ${(download / 1e6).toFixed(1)} Mbps`);
  console.log(`Upload:   ${(upload   / 1e6).toFixed(1)} Mbps`);
  console.log(`Latency:  ${latency} ms`);
};
bypassMinDuration: true tells the engine to proceed to the next measurement regardless of whether the request duration reached the bandwidthFinishRequestDuration threshold. Without it, a fast connection may finish a 100 KB transfer so quickly that the engine keeps trying larger sizes — which is the desired behavior in a full test but not in a quick probe.

Default measurement sequence

The default sequence the engine uses when no custom measurements array is provided:
[
  { type: 'latency', numPackets: 1 },           // Initial TTFB estimation
  { type: 'download', bytes: 1e5, count: 1, bypassMinDuration: true }, // Warm-up probe
  { type: 'latency', numPackets: 20 },           // Full latency measurement
  { 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 sent 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 sequence implements a ramp-up methodology: download and upload measurements start with small file sizes and progressively increase. The engine stops advancing to larger sizes once a request takes longer than bandwidthFinishRequestDuration (default 1000 ms), so slower connections finish sooner and faster connections get tested with appropriately large files.
Packet loss measurement requires a TURN server. The default config points to a deprecated public TURN server. Provide your own via turnServerUri and credentials options, or omit the packetLoss step if packet loss measurement is not needed. See the README for instructions on setting up a Cloudflare Realtime TURN server.

Disabling loaded latency measurement

By default, the engine measures latency in parallel with active download and upload transfers (loaded latency). Disable this to reduce the number of concurrent requests:
import SpeedTest from '@cloudflare/speedtest';

const engine = new SpeedTest({
  measureDownloadLoadedLatency: false,
  measureUploadLoadedLatency: false
});

engine.onFinish = results => {
  // downLoadedLatency and upLoadedLatency will be undefined
  const summary = results.getSummary();
  console.log(summary);
};
OptionDefaultDescription
measureDownloadLoadedLatencytrueMeasure latency concurrently during download transfers.
measureUploadLoadedLatencytrueMeasure latency concurrently during upload transfers.
loadedLatencyThrottle400Milliseconds between consecutive loaded latency requests.
loadedLatencyMaxPoints20Maximum number of loaded latency data points to retain.

Adjusting percentile thresholds

The engine reduces multiple raw measurements down to a single representative value using configurable percentiles:
import SpeedTest from '@cloudflare/speedtest';

const engine = new SpeedTest({
  bandwidthPercentile: 0.8, // Use the 80th percentile for bandwidth (default: 0.9)
  latencyPercentile: 0.5    // Use the median for latency (default: 0.5)
});
bandwidthPercentile (default 0.9) controls which percentile of the collected bandwidth samples is reported as the final value. A higher value selects a faster sample; a lower value is more conservative. The 90th percentile default reflects peak achievable throughput rather than an average.

Custom bandwidth finish duration

bandwidthFinishRequestDuration controls how long a single transfer must take before the engine considers bandwidth fully characterized and stops attempting larger file sizes:
import SpeedTest from '@cloudflare/speedtest';

// Finish after the first request that takes at least 2 seconds
const engine = new SpeedTest({
  bandwidthFinishRequestDuration: 2000
});
ValueEffect
Lower (e.g. 500)Stop ramp-up earlier; shorter test, potentially less accurate result.
Higher (e.g. 2000)Continue ramp-up longer; more accurate result, especially on fast connections.
0Never automatically stop; the full custom measurements array always runs to completion.
If you need a hard time limit on test duration, combine a shorter bandwidthFinishRequestDuration with a smaller custom measurements array that omits the largest file sizes.

Build docs developers (and LLMs) love