A complete multi-tenant platform that uses Workers for Platforms to extend D1, KV, R2, and other Cloudflare services to your end customers with complete isolation.
- 🚀 Dispatch Worker (
my-binding-dispatcher/) - Central routing service that routes requests to their dedicated workers - 👤 Customer Workers (
customer-worker-1/,customer-worker-2/) - Isolated worker instances per customer with dedicated KV and R2 storage instances
✅ Complete Isolation - Each customer has their own worker runtime and storage namespaces
✅ Scalable - Automatically scales
✅ Secure - No cross-customer data access
✅ Extensible - Easy to extend other Cloudflare services (D1, Durable Objects, etc.)
Each customer worker runs the same UserWorker class with the same methods:
uploadFile(),getFile(),deleteFile()for R2 operationssetData(),getData(),deleteData()for KV operationsgetStats(),listFiles(),listData()for analytics
The key difference is that every worker instance is bound to its own, isolated storage resources (KV namespaces, R2 bucket, etc).
// customer-worker-1 bindings
{
"USER_KV": "customer-1-kv-namespace",
"USER_R2": "customer-1-files-bucket"
}
// customer-worker-2 bindings
{
"USER_KV": "customer-2-kv-namespace",
"USER_R2": "customer-2-files-bucket"
}When userWorker.setData('config', {theme: 'dark'}) is called:
- Customer 1's worker → writes to
customer-1-kv-namespace - Customer 2's worker → writes to
customer-2-kv-namespace
Same code, but isolated execution environments.
- Client Request → Dispatcher receives request with
X-User-IDheader - User Resolution → Dispatcher routes request to the worker instance
- Isolated Execution → Customer worker completes KV/R2 operation using the dedicated resources bound to that worker
- Response → Results returned through dispatcher to client
graph TB
Client[Client]
Dispatcher[Dispatcher<br/>]
Worker1[Customer Worker 1<br/>]
Worker2[Customer Worker 2<br/>]
KV1[Customer 1 KV<br/>]
R21[Customer 1 R2<br/>]
KV2[Customer 2 KV<br/>]
R22[Customer 2 R2<br/>]
Client -->|X-User-ID: user1| Dispatcher
Client -->|X-User-ID: user2| Dispatcher
Dispatcher -->|Route user1| Worker1
Dispatcher -->|Route user2| Worker2
Worker1 --> KV1
Worker1 --> R21
Worker2 --> KV2
Worker2 --> R22
style Worker1 fill:#e1f5fe
style Worker2 fill:#e1f5fe
style KV1 fill:#f3e5f5
style R21 fill:#f3e5f5
style KV2 fill:#fff3e0
style R22 fill:#fff3e0
POST /files- Upload files with form-data (key, file)GET /files?key=filename- Download filesDELETE /files?key=filename- Delete files
POST /data- Store JSON data{key, value}GET /data?key=mykey- Retrieve dataDELETE /data?key=mykey- Remove data
GET /stats- Get customer usage statisticsGET /- Health check
# Upload a file for user1
curl -X POST https://your-dispatcher.workers.dev/files \
-H "X-User-ID: user1" \
-F "key=logo.png" \
-F "[email protected]"
# Store data for user2
curl -X POST https://your-dispatcher.workers.dev/data \
-H "X-User-ID: user2" \
-H "Content-Type: application/json" \
-d '{"key": "settings", "value": {"theme": "dark"}}'
# Get user1 statistics
curl https://your-dispatcher.workers.dev/stats \
-H "X-User-ID: user1"# Deploy first customer worker
cd customer-worker-1
npm install
wrangler deploy
# Deploy second customer worker
cd ../customer-worker-2
npm install
wrangler deployUpdate my-binding-dispatcher/wrangler.jsonc with your worker names:
{
"dispatch_namespaces": [{
"binding": "DISPATCHER",
"namespace": "bindings",
"experimental_remote": true
}]
}cd my-binding-dispatcher
npm install
wrangler deploy# Upload a test file
curl -X POST https://your-dispatcher.workers.dev/files \
-H "X-User-ID: test-user" \
-F "key=test.txt" \
-F "[email protected]"Each customer worker needs:
- Unique KV namespace ID
- Unique R2 bucket name
- Same binding names (
USER_KV,USER_R2)
Example wrangler.jsonc:
{
"name": "customer-worker-1",
"kv_namespaces": [{
"binding": "USER_KV",
"id": "your-unique-kv-id"
}],
"r2_buckets": [{
"binding": "USER_R2",
"bucket_name": "customer-1-files"
}]
}- Add D1 binding to customer workers:
{
"d1_databases": [{
"binding": "USER_DB",
"database_name": "customer-1-db",
"database_id": "your-d1-id"
}]
}- Add database methods to customer worker:
async queryDatabase(sql, params) {
return await this.env.USER_DB.prepare(sql).bind(...params).all();
}Feel free to use this to build your own multi-tenant platforms!