Setup
Deploy ZBSearch Edge to Cloudflare Workers and R2 - step by step from zero to a live search API. Cloudflare deploy is currently in beta.
Beta
Cloudflare deploy is in beta. APIs, defaults, and CLI behavior may change. Pin package versions in production and review release notes before upgrading.
This guide deploys ZBSearch Edge to your Cloudflare account. You do not need to clone the repository - install the published npm packages and run the setup CLI.
Quick start (npm)
Install the Worker runtime and Wrangler, then run the setup wizard:
mkdir my-search-api && cd my-search-api
npm init -y
npm install @zbsearch/runtime-cloudflare wrangler
npx wrangler login
npx zbsearch-edge-setup --initThe wizard asks for Worker name, R2 bucket names, account ID, optional R2 API credentials, optional read/write API keys, and rebuild settings. It saves zbsearch.edge.config.json in your project directory (gitignore this file), generates wrangler.toml and .env, then provisions and deploys.
Config file instead of wizard
Download the example config (JSON) and edit it:
curl -o zbsearch.edge.config.json https://zbsearch.dev/downloads/zbsearch.edge.config.example.json
# edit zbsearch.edge.config.json - set r2.accountId and R2 API credentials
npx zbsearch-edge-setupYAML is also supported (example):
curl -o zbsearch.edge.config.yaml https://zbsearch.dev/downloads/zbsearch.edge.config.example.yaml
npx zbsearch-edge-setup --config zbsearch.edge.config.yaml| Flag | Purpose |
|---|---|
--dry-run | Preview actions without changes |
--write-only | Only write wrangler.toml and .env |
--skip-deploy | Provision buckets/secrets, skip deploy |
--yes | Skip confirmation prompt |
zbsearch.edge.config.json may contain secrets. Add it to .gitignore and never commit real credentials.
npm packages
| Package | Role |
|---|---|
@zbsearch/runtime-cloudflare | Worker runtime + zbsearch-edge-setup / zbsearch-edge-teardown CLIs |
@zbsearch/edge-index-builder | Rebuild CLI (zbsearch-edge-builder) for production indexes |
@zbsearch/edge-core | Installed automatically as a dependency |
Monorepo development
If you are developing ZBSearch itself, clone the repository and run the setup CLI directly (no root package.json scripts required):
git clone https://github.com/micheleriva/zbsearch.git
cd zbsearch
pnpm install
wrangler login
node packages/runtime-cloudflare/deploy/setup.mjs --deploy-dir deploy/cloudflare --initConfig and generated files live under deploy/cloudflare/. Templates are at packages/runtime-cloudflare/deploy/templates/.
Manual setup
Follow the steps below if you prefer to configure each piece yourself.
Prerequisites
- A Cloudflare account on the Workers Paid plan (see the callout below)
- Node.js 20+
- Wrangler v4+ (installed below)
Log in to Cloudflare from Wrangler:
npx wrangler login1. Install the runtime
mkdir my-search-api && cd my-search-api
npm init -y
npm install @zbsearch/runtime-cloudflare @zbsearch/edge-index-builder wranglerCreate a minimal wrangler.toml in your project directory:
name = "zbsearch-edge"
main = "node_modules/@zbsearch/runtime-cloudflare/src/worker.ts"
compatibility_date = "2025-03-01"No build step is needed - Wrangler bundles the Worker straight from the npm package.
2. Create an R2 bucket
The default configuration expects a bucket named zbsearch-edge. Create it once:
npx wrangler r2 bucket create zbsearch-edgeFor local/preview dev, Wrangler also uses a preview bucket. You can create it optionally:
npx wrangler r2 bucket create zbsearch-edge-previewAdd the bucket binding to wrangler.toml:
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "zbsearch-edge"
preview_bucket_name = "zbsearch-edge-preview"The default configuration (and the setup-CLI template) also includes the Durable Object bindings (write coordinator, plus the per-shard search node used for distributed group searches) and an extended CPU limit:
[[durable_objects.bindings]]
name = "INDEX_COORDINATOR"
class_name = "IndexCoordinator"
[[durable_objects.bindings]]
name = "SEARCH_SHARD"
class_name = "ShardSearch"
[[migrations]]
tag = "v1"
new_sqlite_classes = ["IndexCoordinator"]
[[migrations]]
tag = "v2"
new_sqlite_classes = ["ShardSearch"]
[limits]
cpu_ms = 300000ZBSearch Edge requires the Workers Paid plan. The Free plan caps CPU at 10ms per invocation, which search workloads exceed - see Production. The SQLite-backed Durable Object class above works on both plans.
3. Configure the builder CLI (optional but recommended)
The Worker handles search and WAL writes. Rebuilds that merge the WAL into a new snapshot can run inside the Worker (small indexes) or via an external CLI (production). The same CLI also supports bulk import - building a snapshot locally and uploading it to R2 without per-document API calls.
Create a .env file in your project directory with your R2 credentials from the Cloudflare dashboard:
- Go to R2 -> Overview -> Manage R2 API Tokens
- Create a token with Object Read & Write on your bucket
- Copy the Access Key ID, Secret Access Key, and your Account ID
# .env (gitignore this)
R2_BUCKET=zbsearch-edge
R2_ACCESS_KEY_ID=your_access_key_id
R2_SECRET_ACCESS_KEY=your_secret_access_key
R2_ENDPOINT=https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.comThe Worker binds to R2 natively via Wrangler - it does not need these S3 credentials. They are only for the builder CLI and CI runners.
Rebuild or import against R2 with the builder CLI (installed from npm in step 1):
source .env
npx zbsearch-edge-builder rebuild --all
npx zbsearch-edge-builder import products ./catalog.json \
--create --name products --schema-file ./schema.jsonSee Indexing & rebuilds for import file formats ({ "data": [...] }, JSON array, or JSONL).
4. Optional: protect the API with keys
By default, the API is open. For anything beyond local testing, set two keys — a write key with full access, and a read-only key for search traffic (safe to embed in public clients):
npx wrangler secret put WRITE_API_KEY
npx wrangler secret put READ_API_KEYWhen prompted, enter a strong random string for each. Authenticated requests must send:
Authorization: Bearer YOUR_API_KEYThe write key can do everything (index management, document writes, rebuilds, searches). The read key only covers searches and index/metadata reads — it gets 401 on any write route. If you only set READ_API_KEY, writes are closed entirely. /health and /v1/info remain public so load balancers and uptime checks can probe the Worker without credentials.
The legacy single API_KEY secret still works as a full-access key, but prefer the split above.
5. Deploy the Worker
From your project directory:
npx wrangler deployWrangler prints your Worker URL, for example:
https://zbsearch-edge.your-subdomain.workers.devSave this as WORKER_URL for the examples below.
6. Verify the deployment
Check health:
curl "$WORKER_URL/health"
# {"ok":true}Create an index:
curl -X POST "$WORKER_URL/v1/indexes" \
-H 'content-type: application/json' \
-H "authorization: Bearer YOUR_API_KEY" \
-d '{
"name": "products",
"schema": {
"title": "string",
"description": "string",
"price": "number"
}
}'The index ID is derived from the name (products from "products", my-products from "My Products").
Insert a document (appended to WAL - searchable after rebuild, or sooner via WAL overlay):
curl -X PUT "$WORKER_URL/v1/indexes/products/documents/sku-1" \
-H 'content-type: application/json' \
-H "authorization: Bearer YOUR_API_KEY" \
-d '{"title":"Wireless Headphones","description":"Noise cancelling","price":99}'Trigger a rebuild:
curl -X POST "$WORKER_URL/v1/indexes/products/rebuild" \
-H "authorization: Bearer YOUR_API_KEY"Search:
curl -X POST "$WORKER_URL/v1/indexes/products/search" \
-H 'content-type: application/json' \
-H "authorization: Bearer YOUR_API_KEY" \
-d '{"term":"wireless headphones"}'7. Configure rebuild behavior
Open wrangler.toml. Key settings:
[triggers]
crons = ["*/5 * * * *"]
[vars]
REBUILD_THRESHOLD_OPS = "500"
[limits]
cpu_ms = 300000| Setting | Default | Meaning |
|---|---|---|
crons | Every 5 minutes | Worker checks indexes for pending writes |
REBUILD_THRESHOLD_OPS | 500 | Auto-rebuild in-Worker when pending ops reach this count |
cpu_ms | 300000 (5 min) | Per-request CPU limit (Workers Paid plan); headroom for in-Worker rebuilds |
SNAPSHOT_CACHE_MAX_ENTRIES | 4 | Deserialized snapshots cached per isolate |
SNAPSHOT_CACHE_MAX_BYTES | 67108864 | Byte budget for the per-isolate snapshot cache |
For development and small indexes, the cron + threshold is enough. For production, use an external rebuild runner and set BUILDER_WEBHOOK_URL so the Worker triggers GitHub Actions instead of rebuilding inline.
npx wrangler secret put BUILDER_WEBHOOK_URLCustom domains
To serve the API on your own domain, add a route in the Cloudflare dashboard or extend wrangler.toml:
routes = [
{ pattern = "search.example.com/*", zone_name = "example.com" }
]Then redeploy with npx wrangler deploy.
Teardown (unpublish / reset)
npx zbsearch-edge-teardown --dry-run
npx zbsearch-edge-teardown --yes
npx zbsearch-edge-teardown --keep-buckets --yesMonorepo equivalent: node packages/runtime-cloudflare/deploy/teardown.mjs --deploy-dir deploy/cloudflare --yes
The script reads zbsearch.edge.config.json when present, otherwise wrangler.toml and .env. To deploy again: npx zbsearch-edge-setup.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
404 on search after insert | No rebuild yet | Call POST /v1/indexes/:id/rebuild or wait for cron |
401 Unauthorized | Missing or wrong Authorization header | Set WRITE_API_KEY/READ_API_KEY secrets and send a Bearer token |
| R2 bucket not found on deploy | Bucket name mismatch | Match bucket_name in wrangler.toml to an existing R2 bucket |
| Rebuild CLI fails | Missing S3 credentials | Fill .env with R2 API token values |
| Searches fail with error 1102 | Free plan 10ms CPU cap | Upgrade to Workers Paid |
| Worker CPU timeout on rebuild | Index too large for in-Worker rebuild | Use external builder via BUILDER_WEBHOOK_URL, or bulk import |
What's next
- API reference - full endpoint documentation
- Indexing & rebuilds - understand the write path
- Production - GitHub Actions, monitoring, auth hardening