Cloudflare Wiki

Workers, Next.js, Express, Workers Cache, R2 and API tokens - guides and know-how from production

A hub of our Cloudflare guides plus the short, frequently needed procedures that do not fit anywhere else: account API tokens, deploying to several accounts and bulk work with R2 from the command line. Every command can be copied with one click.

Vladimír NeporAuthorVladimír Nepor
PublishedUpdated
Cloudflare hub map: your app (Next.js, Express, static files) is deployed with Wrangler to a Cloudflare Worker. Workers Cache answers in front of the Worker, D1 and R2 are bound to it. Four cards lead to the guides and to the token and R2 sections.Your appNext.jsExpressstatic filesCloudflare networkwrangler deployWorkers CacheHIT ≈ 50 msWorkeryour codeD1 · SQLR2 · objectsAPI token · .env · aws s3 → R2GUIDE · NEXT.JSNext.js on CloudflareOpenNext Worker, D1, R2, deployopen →GUIDE · EXPRESSExpress on Workersnodejs_compat, bindings, Handlebarsopen →GUIDE · EDGE CACHEWorkers Cachecache in front of the Worker, purgeopen →HERE · CLITokens and R2 from CLIdeploy with a token, sync to R2open →
Contents

00 · Overview

Cloudflare at Vrealmatic

This is the entry point to everything we have written about Cloudflare. Bigger topics have their own guides (Next.js, Express, Workers Cache). Short, frequently needed know-how that does not belong to any of them stays right here: API tokens, deploying to several accounts with a token, and working with R2 object storage from the command line.

What do you need to solve?

QuestionWhere to find the answer
I want to run a Next.js app on Cloudflare Workers.Next.js on Cloudflare →
I have an Express API and want it on a Worker.Express on Workers →
Pages should be served from the edge without running the Worker.Workers Cache →
One app for several countries, each on its own domain.Multi-country website →
Which token do I need, account or user?API tokens →
I deploy to several Cloudflare accounts (clients, projects).Deploy with a token →
I need to upload or delete many files in R2.R2 from the command line →
Will the free plan be enough?Limits at a glance →
Scope

State as of October 2026: Wrangler 4, account-owned API tokens, Workers Cache (launched July 2026). Everything here runs in production on our own websites, including this one (Next.js on OpenNext, D1, R2, Workers Cache).

01 · Guides

Guides

02 · API tokens

Account and user API tokens

Every non-interactive access to Cloudflare (Wrangler in CI, deploys to a client's account, scripts) needs an API token. Cloudflare now has two kinds and for automation the account token is the better choice.

Account token or user token

Account API tokenUser API token
WhereManage Account → Account API TokensMy Profile → API Tokens
Belongs tothe account (a service principal)a specific person
When a person leaveskeeps workingstops working once the user is removed from the account
Typical useCI/CD, deploys, integrationsyour own scripts and experiments
Recognisable byprefix cfat_-

Creating a token safely

  • Start from a template. For deploying Workers use Edit Cloudflare Workers and only add what the project really uses (D1, R2, KV).
  • Limit the resources to one account and, where zone permissions are needed, to specific zones (Zone Resources).
  • Client IP Address Filtering restricts the token to your office, VPN or CI runner addresses. A leaked token is then useless elsewhere.
  • TTL (expiry) for tokens you need only temporarily. A token for a one-off migration should not live forever.
Shown only once

Cloudflare displays the token value only right after creation. Store it in a password manager or directly in the CI secrets. If it is lost, roll the token or create a new one. Account tokens are not supported by a few products (e.g. Page Rules, Registrar, Turnstile), use a user token there.

03 · Deploy

Deploy with a token to several accounts

npx wrangler login signs you in through OAuth as one user. When you deploy to several Cloudflare accounts (your own, clients'), it is more reliable to give each project its own account token in a .env file. Wrangler reads it automatically.

Step 1: Log out of the OAuth session

So that it is clear which identity Wrangler uses (a token in the environment takes precedence over OAuth anyway).

Terminal
npx wrangler logout

Step 2: Create an account token in each target account

Manage Account → Account API Tokens → Create Token, template Edit Cloudflare Workers, scoped to that single account (see creating a token safely). Copy the Account ID too: on Account home click the dots next to the account name and choose Copy account ID.

Step 3: Put the token into .env in the project root

.env
CLOUDFLARE_ACCOUNT_ID="<ACCOUNT_ID>"
CLOUDFLARE_API_TOKEN="<API_TOKEN>"
Terminal
printf '.env\n.env.*\n' >> .gitignore

The .env file must never get into git. With several environments you can use .env.<environment> files as well.

Step 4: Verify which account Wrangler sees

The output must show the token and the account you expect. Otherwise check the variable names and that you run the command in the project root.

Terminal
npx wrangler whoami

Step 5: Deploy

Terminal
npx wrangler deploy
# or the project's own script, e.g. npm run deploy
Remember
  • One project = one account token in its own .env file. Switching between accounts is then just switching between projects.
  • wrangler logout does not revoke a token from .env. A token that is no longer needed must be deleted in the dashboard.
  • The same two variables work in CI (GitHub Actions, GitLab): store them as secrets, not in the repository.

04 · R2

R2 Object Storage from the command line

R2 is S3-compatible object storage without egress fees. For a few files Wrangler is enough, for bulk uploads, sync and deleting whole prefixes use the AWS CLI (or rclone) with an R2 token. We use it e.g. to clear the page-context cache of this website.

Step 1: Create an R2 API token

R2 Object Storage → Account Details → API Tokens: Manage, then Create Account API token (or a user token). Choose the permission level: Admin Read & Write for work across buckets, Object Read & Write limited to specific buckets where possible. Add a TTL and IP filtering if the form offers them. Of the values shown you need the Access Key ID and the Secret Access Key (shown only once) and the endpoint https://<ACCOUNT_ID>.r2.cloudflarestorage.com.

Step 2: Store the keys as an AWS CLI profile

A separate profile r2 keeps R2 apart from any real AWS credentials. R2 ignores the region, but the CLI requires one, so use auto.

~/.aws/credentials
[r2]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>
~/.aws/config
[profile r2]
region = auto
# Only if an upload fails with a checksum error (older CLI/SDK setups):
# request_checksum_calculation = when_required
# response_checksum_validation = when_required

Newer AWS CLI versions send CRC checksums by default. R2 supports them now, the two commented lines are only a fallback if an upload fails with a checksum error.

Step 3: Upload a directory to R2

sync only uploads new and changed files. The output goes to a log, which is handy for thousands of files.

Terminal
aws s3 sync <SOURCE_DIR> s3://<BUCKET>/<TARGET_PREFIX>   --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com   --profile r2   --exact-timestamps   > r2-upload.log 2>&1

Step 4: Delete files: first a dry run

With --dryrun the command only lists what it would delete. When the list is right, run it again without --dryrun.

Terminal
aws s3 rm s3://<BUCKET>/<PREFIX_TO_REMOVE>   --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com   --profile r2   --recursive   --dryrun
Terminal
# Example: drop the cached page context (v1) of this website
aws s3 rm s3://cache/cache_context/v1   --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com   --profile r2   --recursive

Alternatives: Wrangler and rclone

Wrangler handles single objects and bucket settings (CORS, lifecycle rules, custom domains), but has no bulk or sync command. For large transfers rclone is a good alternative to the AWS CLI.

Terminal
# Single objects (no bulk / sync command in Wrangler)
npx wrangler r2 object put <BUCKET>/<KEY> --file ./local/file.json --remote
npx wrangler r2 object get <BUCKET>/<KEY> --remote
npx wrangler r2 object delete <BUCKET>/<KEY> --remote
~/.config/rclone/rclone.conf
[r2]
type = s3
provider = Cloudflare
access_key_id = <ACCESS_KEY_ID>
secret_access_key = <SECRET_ACCESS_KEY>
endpoint = https://<ACCOUNT_ID>.r2.cloudflarestorage.com
acl = private
# required with an Object-level token (bucket-scoped)
no_check_bucket = true
Terminal
rclone sync <SOURCE_DIR> r2:<BUCKET>/<TARGET_PREFIX> --dry-run
# remove --dry-run once the list of changes looks right

05 · Limits

Free and paid plan limits at a glance

The free plan is enough for a surprising amount, but it has a few hard limits. The overview shows the ones you hit first; prices and limits change, so check the linked documentation before you decide.

FreeWorkers Paid
Worker requests100,000 per dayno daily limit, billed per request
CPU time per request10 ms30 s by default, up to 5 min
Subrequests per request5010,000
Static assets per version20,000 files100,000 files
D15M rows read and 100k written per day, 5 GB25 billion reads and 50M writes per month included
R2 (Standard)10 GB-month, 1M Class A and 10M Class B operations per month$0.015 / GB-month above the free tier; egress always free
Workers Cacheavailablea HIT is billed as a request, no CPU time

The 10 ms CPU limit on the free plan is usually what decides it for server-side rendering (Next.js on OpenNext). With Workers Cache in front of the Worker most requests do not run it at all.

Sources

Official documentation

Building on Cloudflare?

We set up Workers, OpenNext, D1, R2 and Workers Cache for your project.

Contact Us