Cloudflare R2 Storage
Store your Hot Updater bundles in Cloudflare R2.
Installation
npm install @hot-updater/cloudflare --save-devFor a Worker or other custom server, install the runtime dependencies in the server project:
npm install @hot-updater/server @hot-updater/cloudflareSetup
For a complete provider deployment, follow the Cloudflare setup. For an existing bucket or a custom server, use the storage configuration below; you do not need to provision the provider's database and server to use this adapter.
Create R2 S3-compatible credentials from the Cloudflare R2 API token page or follow the Cloudflare R2 API token guide.
Configuration
import type { S3ClientConfig } from "@aws-sdk/client-s3";
interface R2S3StorageConfig extends S3ClientConfig {
accountId: string;
bucketName: string;
credentials: NonNullable<S3ClientConfig["credentials"]>;
basePath?: string;
}r2Storage requires S3-compatible credentials for its Node.js/CLI transport.
It accepts the AWS SDK's endpoint option and defaults to
https://<account-id>.r2.cloudflarestorage.com. For a bucket restricted to a
jurisdiction, set its corresponding endpoint; for example,
endpoint: "https://<account-id>.eu.r2.cloudflarestorage.com" for an EU
bucket. See Cloudflare's jurisdiction-specific endpoints
for EU, FedRAMP, and US buckets.
Usage
import { r2Storage } from "@hot-updater/cloudflare";
import { defineConfig } from "hot-updater";
export default defineConfig({
// ... build, database, plugins, and updateStrategy
storage: r2Storage({
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
bucketName: "my-r2-bucket",
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
}),
});Runtime usage
The root r2Storage adapter implements upload, read, download URL,
existence-check, and exact-delete operations. A download URL is an R2
presigned URL that expires in an hour, signed with the adapter's S3-compatible
credentials, so a self-hosted createHotUpdater server can take the same
adapter: devices download from the bucket, which stays private.
Cloudflare Worker bindings
Inside a Worker, import r2Storage from @hot-updater/cloudflare/worker and
capture the native R2 binding when constructing the adapter:
import { d1Database, r2Storage } from "@hot-updater/cloudflare/worker";
import { createHotUpdater } from "@hot-updater/server";
import { apiKeys, insights } from "@hot-updater/server/plugins";
import { env } from "cloudflare:workers";
export const hotUpdater = createHotUpdater({
database: d1Database(env.DB),
plugins: [insights(), apiKeys()],
storage: r2Storage({
bucket: env.BUCKET,
bucketName: env.BUCKET_NAME,
accountId: env.ACCOUNT_ID,
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
},
}),
});Configure DB and BUCKET as D1 and R2 bindings, set BUCKET_NAME to the
actual bound bucket name and ACCOUNT_ID to your account ID, and store the
CLI configuration's S3-compatible credentials as the Worker secrets
R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY. The Worker reads and writes
objects through its R2 binding and presigns download URLs with the
credentials, as the root r2Storage does: devices download from the private
bucket. Without accountId and credentials, the adapter has no download
URLs, which a Console that only reads bundles does not need. Use the same
bucket and optional basePath in both configurations.
Mount hotUpdater.handlers.client through your
server framework. Bindings are
already captured by the adapters; invoke a handler with only the Request.
Protocol
r2://bucket/encoded/key
For example, r2://updates/releases/1.0.0/bundle%20ios.zip is stored in the
storageUri field. Keys retain their slash hierarchy and encode each segment.