HotupdaterHot Updater
Storage Adapters

Cloudflare R2 Storage

Store your Hot Updater bundles in Cloudflare R2.

Installation

npm install @hot-updater/cloudflare --save-dev

For a Worker or other custom server, install the runtime dependencies in the server project:

npm install @hot-updater/server @hot-updater/cloudflare

Setup

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

hot-updater.config.ts (excerpt)
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:

src/hotUpdater.ts
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.

On this page