Library / SDK
cloudflare/cloudflare-typescript avatar
cloudflare/cloudflare-typescript

cloudflare-typescript: a generated client with typed errors and default retries

The official TypeScript library for the Cloudflare API

822 stars197 forksTypeScriptApache-2.0

At a glance

What is it?
cloudflare-typescript is the official TypeScript library for the Cloudflare REST API, generated with Stainless. It ships typed request parameters, one error class per HTTP status, two automatic retries on transient failures, and automatic pagination, but its documented install command still points at a git+ssh URL rather than the npm registry.
Who is it for?
cloudflare-typescript is a good fit for a server-side Node or TypeScript service that manages zones, KV namespaces, or DNS and wants the API surface to stay in step with the REST documentation, since the client is regenerated from the API definition.
Can I use it commercially?
Yes. Apache-2.0 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 20 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

A client generated from the API definition, not hand-written

The library is described as the official TypeScript library for the Cloudflare API, providing convenient access to the Cloudflare REST API from server-side TypeScript or JavaScript. The important structural fact is stated in one line: the library is generated with Stainless, a code generation service for API clients.

That fact changes how you should read the rest of the documentation. The full API is not described in the README; it is described in api.md, a generated file that lists every resource, method, parameter, and response field. The same applies to the docstrings on every method, which is where the per-endpoint documentation lives, and where your editor shows it on hover.

The consequence for a user is that the library tracks the REST API rather than expressing a hand chosen opinion about it. When Cloudflare adds a field, the client gets it. When Cloudflare changes a field, the type changes with it and your compiler points at the call site. The cost is that you inherit the API's shape, including its inconsistencies, and that the surface area of a client covering the whole Cloudflare API is large.

The repository reflects a generated codebase. There is a.stats.yml file, a scripts directory, a jest.config.ts, several tsconfig variants including one for Deno, an eslint config, and a scripts/ directory with shell entry points for test, build, lint and format. The package.json wires each of those to a script.

json
{
  "name": "cloudflare",
  "version": "7.3.0",
  "description": "The official TypeScript library for the Cloudflare API",
  "author": "Cloudflare <[email protected]>",
  "types": "dist/index.d.ts",
  "main": "dist/index.js",
  "type": "commonjs",
  "license": "Apache-2.0",
  "packageManager": "[email protected]"
}

The documented install command points at a git URL, not npm

This is the first thing that will surprise you. The installation section gives one command, and it installs from the GitHub repository over SSH rather than from the npm registry.

bash
npm install git+ssh://[email protected]:cloudflare/cloudflare-typescript.git

The README explains the situation in a note: once the package is published to npm, the command becomes npm install cloudflare. The repository does carry an npm badge for the cloudflare package, so the registry entry and the documented command are not yet the same thing, and the note is dated to the generation pipeline rather than to a release.

The practical consequence is that installing as documented requires SSH access to the GitHub repository, since the URL uses the git+ssh scheme rather than https. On a CI runner without a deploy key, that step fails before you get to any API call. A workaround is to install the published registry package under its own name, but then you are no longer following the README and you have to verify that the registry version matches the repository version you were reading.

Two other things in the package metadata are worth knowing before you commit to it. The runtime dependencies map is empty, so the published package brings no transitive runtime dependencies with it, which is unusual for a client of this size. And there is a bin entry that exposes a cloudflare command pointing at bin/cli, so the package ships a command line entry point as well as a library.

The repository also carries MIGRATION.md, a CHANGELOG.md, and a SECURITY.md, and the version is at 7.3.0 with the major number already at seven. A major version number that has climbed to seven in a generated SDK usually means breaking changes between majors, so the migration file is the document to read before you jump versions.

Typed parameters and response fields for every call

The library includes TypeScript definitions for all request parameters and response fields, and the README shows the pattern for using them. Instead of passing an object literal straight into a method, you can declare it with an exported params type and get the same object checked by the compiler.

ts
import Cloudflare from 'cloudflare';

const client = new Cloudflare({
  apiToken: process.env['CLOUDFLARE_API_TOKEN'], // This is the default and can be omitted
});

const params: Cloudflare.ZoneCreateParams = {
  account: { id: '023e105f4ecef8ad9ca31a8372d0c353' },
  name: 'example.com',
  type: 'full',
};
const zone: Cloudflare.Zone = await client.zones.create(params);

The value of this is not ceremony. The params type marks required and optional fields, and the response type gives you the shape of what comes back, so a renamed field in the API becomes a type error rather than an undefined at runtime.

The client is constructed with an apiToken option, and the comment in the example notes that this is the default and can be omitted, which means the token is picked up from the environment when you do not pass it. The resource hierarchy mirrors the API, so a zone create is client.zones.create and a KV value update is client.kv.namespaces.values.update.

The API documentation itself lives at developers.cloudflare.com/api, and the README points there for the REST API while pointing to api.md for the library.

One error class per HTTP status, all under APIError

Error handling is where a generated client either earns its place or does not, and this one is explicit. When the library cannot connect to the API, or the API returns a non-success status code, a subclass of APIError is thrown.

ts
const zone = await client.zones
  .get({ zone_id: '023e105f4ecef8ad9ca31a8372d0c353' })
  .catch(async (err) => {
    if (err instanceof Cloudflare.APIError) {
      console.log(err.status); // 400
      console.log(err.name); // BadRequestError
      console.log(err.headers); // {server: 'nginx', ...}
    } else {
      throw err;
    }
  });

The status, the error name, and the response headers are all on the error object, and the class name tells you the category without parsing a message.

The status to class mapping is given as a table, and it is worth reading as a policy statement rather than a list. A 400 becomes a BadRequestError, 401 an AuthenticationError, 403 a PermissionDeniedError, 404 a NotFoundError, a 422 an UnprocessableEntityError, and a 429 a RateLimitError. Anything at or above 500 becomes an InternalServerError, and a failure to connect at all becomes an APIConnectionError with no status code.

That mapping is what lets you write a catch block that distinguishes a bad token from a missing zone from a rate limit, which is a distinction you would otherwise have to build from status codes yourself.

Two retries by default, and 409 Conflict is one of them

The retry policy is the setting most likely to surprise you in production, so it deserves close reading.

Certain errors are retried automatically twice by default, with a short exponential backoff. The list of qualifying conditions is explicit: connection errors such as a network connectivity problem, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and server errors at or above 500.

The 409 in that list is the notable one. A conflict is often a legitimate response to a request that has already taken effect, and the default policy will resend it. For a create call, that means a request which succeeded on the server but reported a conflict can be sent again, and for a create that is not idempotent the second attempt is a second zone, a second record, or a second charge, depending on the endpoint. The library does not distinguish idempotent from non-idempotent methods in the retry policy described here.

The retry count is configurable, and it can be set globally on the client or per request.

js
// Configure the default for all requests:
const client = new Cloudflare({
  maxRetries: 0, // default is 2
});

// Or, configure per-request:
await client.zones.get({ zone_id: '023e105f4ecef8ad9ca31a8372d0c353' }, {
  maxRetries: 5,
});

Setting maxRetries to 0 on the client turns retries off entirely for every request, which is the blunt instrument to reach for when a job must not double-create anything.

One minute timeouts that are also retried

Requests time out after one minute by default, and the timeout is configurable either on the client or per request.

ts
// Configure the default for all requests:
const client = new Cloudflare({
  timeout: 20 * 1000, // 20 seconds (default is 1 minute)
});

// Override per-request:
await client.zones.edit({ zone_id: '023e105f4ecef8ad9ca31a8372d0c353' }, {
  timeout: 5 * 1000,
});

On a timeout the library throws an APIConnectionTimeoutError. The documentation then adds the line that matters for capacity planning: requests which time out are retried twice by default.

Put those two numbers together and the worst case is obvious. A one minute timeout with two retries means a single call can occupy a request for roughly three minutes before it gives up, and your own timeout setting multiplies that rather than replacing it. A thirty second timeout with the default retry count still runs for about ninety seconds.

For a control plane that provisions DNS and zones, that is usually fine. For anything running inside a short lived job, a serverless function with its own execution limit, or a request handler that has a deadline of its own, the effective timeout is not the number you set. If the call must not exceed the surrounding budget, set maxRetries to 0 on that request as well as lowering the timeout, otherwise the retry loop will outlive the caller.

Pagination is handled two ways, one of them implicit

List methods in the Cloudflare API are paginated, and the library offers a way to walk all pages without thinking about page tokens, plus a way to drive the pages yourself.

The implicit one is an async iterator. A for await loop over the list method fetches further pages as needed.

ts
async function fetchAllAccounts(params) {
  const allAccounts = [];
  // Automatically fetches more pages as needed.
  for await (const account of client.accounts.list()) {
    allAccounts.push(account);
  }
  return allAccounts;
}

The explicit one gives you the current page, whose result array holds the items, plus helper methods for walking the rest of the sequence yourself.

ts
let page = await client.accounts.list();
for (const account of page.result) {
  console.log(account);
}

// Convenience methods are provided for manually paginating:
while (page.hasNextPage()) {
  page = await page.getNextPage();
  // ...
}

The first form is short but has a cost that the documentation does not spell out: it collects every item of every page into the array you return, and it keeps fetching until the API runs out of pages. For an account list that is a handful of records it is ideal. For a resource that returns a large number of records per page, the same loop becomes a memory problem, and the explicit form is the one that lets you process a page at a time and stop early.

File uploads accept four shapes, and there is a tree-shakable entry point

Two smaller features are worth their own paragraphs, because both are places where a generated client usually falls short.

The first is file uploads. A parameter that corresponds to an upload can be given in several forms: a File object or something with the same structure, a fetch Response or something with the same structure, an fs.ReadStream, or the return value of a toFile helper. The README shows each of them against a KV namespace value update.

ts
import fs from 'fs';
import Cloudflare, { toFile } from 'cloudflare';

const client = new Cloudflare();
ts
await client.kv.namespaces.values.update('My-Key', {
  account_id: '023e105f4ecef8ad9ca31a8372d0c353',
  namespace_id: '0f2ac74b498b48028cb68387c421e279',
  value: await toFile(Buffer.from('my bytes'), 'file'),
});

The toFile helper covers the case where none of the platform specific forms are available, and it accepts both a Buffer and a typed array.

The second is tree shaking. A client covering the entire Cloudflare API is large, so the library exposes an entry point that lets you construct a client with only the resources you need.

ts
import { createClient } from 'cloudflare/tree-shakable';
import { Zones } from 'cloudflare/resources/zones/zones';

That import is the documented entry point for a smaller bundle, and it is the option to reach for when the client is used in a browser bundle or a worker rather than on a server. The repository also carries an examples directory with subdirectories for ai and workers, which are the two contexts the examples are organised around.

Editorial conclusion

cloudflare-typescript is a good fit for a server-side Node or TypeScript service that manages zones, KV namespaces, or DNS and wants the API surface to stay in step with the REST documentation, since the client is regenerated from the API definition. It is a poor fit for a script that only makes one call, where a plain fetch against the same endpoint is shorter, and for a browser bundle that does not need the whole surface, where the tree-shakable entry point is the piece to look at. Verify first that the version you install matches the API changes your code depends on, that the default retry count of two does not duplicate a non-idempotent create call, and that the timeout you set is compatible with the retry policy, since timed out requests are retried as well.

Frequently asked questions

How do I install the cloudflare TypeScript library?

The README documents npm install git+ssh://[email protected]:cloudflare/cloudflare-typescript.git, and notes that this will become npm install cloudflare once the package is published to npm. The git URL uses SSH, so installing as documented needs GitHub SSH access on the machine running it.

How does the cloudflare-typescript client handle API errors?

A subclass of APIError is thrown when the API returns a non-success status or when the client cannot connect. Each status maps to its own class, such as BadRequestError for 400, AuthenticationError for 401, PermissionDeniedError for 403, NotFoundError for 404, and RateLimitError for 429, and the error exposes status, name, and headers.

Does the cloudflare-typescript client retry failed requests?

Yes. Connection errors, 408 Request Timeout, 409 Conflict, 429 Rate Limit, and server errors of 500 or more are retried twice by default with a short exponential backoff. The count is configurable with maxRetries, globally on the client or per request, and 0 disables retries.

How do I list every page of results with the cloudflare TypeScript client?

Use a for await loop over the list method, which fetches further pages as needed, or drive the pagination manually with page.result, page.hasNextPage(), and page.getNextPage(). The manual form is the one to prefer when the result set is large enough that you do not want it all in memory at once.

What is the request timeout of the cloudflare-typescript client?

Requests time out after one minute by default, and the timeout option changes that for the whole client or for a single request. A timeout raises an APIConnectionTimeoutError, and timed out requests are retried twice by default, so the total time a call can take is longer than the timeout you set.

Official sources

  1. cloudflare/cloudflare-typescript on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cloudflare-cloudflare-typescript.svg)](https://hysenlabs.com/projects/cloudflare-cloudflare-typescript)

Was this useful?