Cloudflare Speedtest, the measurement engine behind speed.cloudflare.com
Component to perform network speed tests against Cloudflare's edge network
At a glance
- What is it?
- cloudflare/speedtest is the JavaScript module that powers the Cloudflare speed test site, measuring download and upload bandwidth, latency and packet loss against the edge network using the browser's own PerformanceResourceTiming API. It ships as a small ESM package with an experimental path for attributing tests to a registered customer.
- Who is it for?
- Adopt cloudflare/speedtest if you want network quality measurement in your own application and you are willing to accept that Cloudflare collects the results. Skip it if your product must measure without reporting anything to a third party, or if you need to measure against your own infrastructure rather than the Cloudflare edge.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the module measures and how
The @cloudflare/speedtest package is a JavaScript module to measure the quality of a client's Internet connection, and it is the measurement engine that powers the Cloudflare speed test application at speed.cloudflare.com.
The mechanism is worth understanding because it determines both the accuracy and the limits of the result. The module performs test requests against the Cloudflare edge network and relies on the browser's PerformanceResourceTiming API to extract timing results. There is no native binary, no socket and no server-side component involved: the browser makes ordinary requests and the browser's own timing API reports how long they took.
That design has a clear advantage in that it measures the connection as the user experiences it, including the browser's own stack, the HTTP version negotiated and whatever middleboxes are in the path. The trade-off is that the resolution and the semantics are those of the browser rather than of a dedicated client, and the numbers are not directly comparable with the results of a command line tool that opens its own sockets.
The connection is characterised by download and upload bandwidth, latency and packet loss, and the packet loss measurement is the one that needs something extra. The TURN server URI option points at turn.cloudflare.com:3478, and the module measures packet loss through it, which means the TURN path is a separate measurement from the download and upload path.
One behaviour is stated plainly and belongs in any evaluation. Measurement results are collected by Cloudflare on completion for the purpose of calculating aggregated insights regarding Internet connection quality. The module is a client for a measurement service, not a purely local instrument, and the results leave the browser when the test finishes.
The package itself is small and modern in shape. It is ESM only, with type set to module and a single export pointing at dist/speedtest.js, it declares sideEffects as false, and it is built with tsdown. The version is 1.14.2, the licence is MIT, and the author listed in package.json is Vasco Asturiano.
The API surface and the shape of a test run
SpeedTest is a JavaScript class and should be instantiated with the new keyword. It is not required to pass a config object, because all items have default values, which makes the simplest usage a single line:
import SpeedTest from '@cloudflare/speedtest';
new SpeedTest().onFinish = results => console.log(results.getSummary());The lifecycle is controlled through three methods. play() starts or resumes the measurements and does nothing if the engine is already running or is finished. pause() pauses the measurements and does nothing if it is already paused or finished. restart() clears the current results and restarts from the beginning.
Three attributes describe the state. The results getter returns the current results object, and the README warns that it may yield incomplete values if the test is still running. The isRunning getter says whether the engine is currently running, and isFinished says whether it has completed all measurements and the results are final.
Notifications are delivered through callbacks. onRunningChange is invoked whenever the engine starts or stops, with the current state as an argument. onResultsChange is invoked whenever any item in the results changes, which usually indicates the completion of a measurement. onFinish is the one used in the example, and it is the point at which the results are final and the summary is worth reading.
The onResultsChange callback is the one that matters for a user interface, because it fires per measurement rather than once at the end. A progress bar that only updates at the end is a poor progress bar, and the README describes the argument as an object with a type string.
Installation is one command:
npm install @cloudflare/speedtestThe package is published under the @cloudflare scope, so the scope matters if you mirror from a private registry.
The configuration options that change what is measured
The option table is long, and most of it is about how the measurement is shaped rather than about the endpoints. A few of them are worth calling out because they change the meaning of the result.
The endpoints are configurable: downloadApiUrl defaults to https://speed.cloudflare.com/__down and uploadApiUrl to https://speed.cloudflare.com/__up. The TURN server for packet loss defaults to turn.cloudflare.com:3478, with credential and username options for cases where your own TURN server is used. Pointing these elsewhere is what lets you run the same engine against your own infrastructure.
The loaded latency options are the ones that change what latency means. The measureDownloadLoadedLatency and measureUploadLoadedLatency flags, both on by default, run additional latency requests at the same time as the bandwidth requests, which gives you latency under load rather than latency on an idle connection. The loadedLatencyThrottle option, 400 milliseconds by default, is the interval between those extra requests, and loadedRequestMinDuration, 250 milliseconds, decides when a request counts as loading the connection.
The percentile options decide which sample is reported. The latencyPercentile defaults to 0.5, so the median, and the bandwidthPercentile defaults to 0.9, so the top decile. That asymmetry is deliberate: bandwidth is usually reported at the high end because that is what a download feels like, and latency at the median because the median is what a user experiences.
The duration options control when to stop. bandwidthFinishRequestDuration, 1000 milliseconds, is the minimum duration to reach before the engine halts further measurements with larger file sizes in the same direction, and bandwidthAbortRequestDuration, zero and therefore disabled, is the minimum duration to reach for aborting the test early.
The estimatedServerTime option exists for when the download and upload APIs do not return a server-timing response header, in which case a fixed value is subtracted from all time-to-first-byte calculations. It defaults to zero, which means the raw time-to-first-byte is used unless you know the server-side overhead.
The measurements option is an array giving the sequence of measurements to perform, and the README points to a separate section for the syntax.
The experimental attribution path and the repository layout
Three options are marked experimental, and the README's warning about them is unusually blunt: they may change behaviour, be renamed, or be removed in any release, including a patch, and you should not depend on them.
They are the authorizationToken, authorizationEnabled and allowInsecureAuthorizationToken trio, and together they let a customer attribute a test to their own account rather than to an anonymous user. The authorizationToken is an opaque token sent as an Authorization: Bearer header on the measurement, TURN credential and results-logging requests, and the README is explicit that you obtain it from your own backend and that the engine never requests one itself. It also requires authorizationEnabled, and on its own it is never sent.
The authorizationEnabled option opts in to sending the token, which is otherwise withheld even when set. The value 'header' and the value true both send it as a Bearer header, and true is described as a permanent alias for header, so a future transport can be added later without changing what an existing configuration does. That is a small piece of API design thoughtfulness that tells you the author anticipated changing the transport.
The third option, allowInsecureAuthorizationToken, sends the token even when the endpoint is not HTTPS, for local development against an http:// server. The README says never enable it in production, because the server treats a token seen over plaintext as compromised and revokes it.
The repository layout is modern TypeScript. There is a src/ directory, tests/ with vitest, an example/ directory with a basic-page and a turn-worker example, an AGENTS.md, a.husky directory and a.nvmrc. The tooling is oxlint and oxfmt for linting and formatting, tsdown for the build, and TypeScript 7 for the type check. The lint script runs all three in sequence, which is the shape of a project that treats type checking as part of linting.
The test scripts are split into unit and end-to-end projects, with vitest run against them separately, and the Playwright dependency with the vitest browser project suggests the end-to-end tests drive a real browser, which is the only way to test something that depends on the browser's own timing API.
The release history shows frequent work: v1.14.0 in September 2026, v1.14.1 later the same month and v1.14.2 in October, with the last push on 2026-10-21.
Editorial conclusion
Adopt cloudflare/speedtest if you want network quality measurement in your own application and you are willing to accept that Cloudflare collects the results. Skip it if your product must measure without reporting anything to a third party, or if you need to measure against your own infrastructure rather than the Cloudflare edge. Verify first by running the module against a known-good connection and comparing the summary with the public speed test page, then confirm your own privacy copy matches the collection behaviour the README describes.
Frequently asked questions
What does the cloudflare/speedtest package measure?
It measures download and upload bandwidth, latency and packet loss of a client's Internet connection, by making test requests against the Cloudflare edge network and reading the results from the browser's PerformanceResourceTiming API.
How do I run a speed test with the module?
Install it with npm install @cloudflare/speedtest, then create the class and assign a callback: new SpeedTest().onFinish = results => console.log(results.getSummary()). You can also control the run with play, pause and restart, and react to per-measurement updates with onResultsChange.
Are the speed test results sent to Cloudflare?
Yes. The README states that measurement results are collected by Cloudflare on completion for the purpose of calculating aggregated insights regarding Internet connection quality, so the module is a client for a measurement service rather than a purely local instrument.
Can I measure against my own server instead of the Cloudflare edge?
Yes. The downloadApiUrl, uploadApiUrl and turnServerUri options are all configurable, and the TURN credentials can be supplied with turnServerCredsApiUrl, turnServerUser and turnServerPass, which is what lets the same engine run against your own infrastructure.
What are the experimental options in the speed test module?
The authorizationToken, authorizationEnabled and allowInsecureAuthorizationToken options, which together attribute a test to a registered customer. The README warns that they may change or be removed in any release including a patch, and that the insecure variant must never be enabled in production because a token seen over plaintext is treated as compromised and revoked.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cloudflare-speedtest)