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);
};
| Option | Default | Description |
|---|
measureDownloadLoadedLatency | true | Measure latency concurrently during download transfers. |
measureUploadLoadedLatency | true | Measure latency concurrently during upload transfers. |
loadedLatencyThrottle | 400 | Milliseconds between consecutive loaded latency requests. |
loadedLatencyMaxPoints | 20 | Maximum 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)
});
Bandwidth percentile
Latency percentile
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.
latencyPercentile (default 0.5) controls which percentile of collected round-trip time samples is reported. The default median is a robust central estimate that resists outlier spikes.
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
});
| Value | Effect |
|---|
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. |
0 | Never 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.