Operator Guide
Operator Guide
Step-by-step instructions for deploying, configuring, and maintaining the admin system.
Prerequisites
| Requirement | Details |
|---|---|
| Clerk account | clerk.com — provides identity and JWT authentication |
| Cloudflare account | Workers Paid plan (required for D1, KV, Analytics Engine) |
| Wrangler CLI | npm i -g wrangler (v3+) — used for D1 management and deployments |
| Git access | Clone of the bloqr-backend repository |
1. Create the ADMIN_DB Database
The admin system uses a dedicated D1 database separate from the application database.
# Create the databasewrangler d1 create bloqr-backend-admin-dbThis outputs a database_id UUID. Copy it — you’ll need it in the next step.
Update wrangler.toml
The ADMIN_DB binding is pre-configured but commented out in wrangler.toml. Uncomment it and insert your database ID:
[[d1_databases]]binding = "ADMIN_DB"database_name = "bloqr-backend-admin-db"database_id = "<paste-your-database-id-here>"migrations_dir = "admin-migrations"Why a separate database? The admin system uses its own D1 instance (
ADMIN_DB) as an edge cache, with Neon PostgreSQL (via Hyperdrive) as the authoritative data store. D1 provides low-latency reads at the edge while Neon holds the durable source of truth. TheADMIN_DBbinding is separate from the application’sDBbinding for blast-radius isolation — a bad admin migration or runaway query cannot corrupt application data.
2. Run Migrations
The schema is defined in admin-migrations/0001_admin_schema.sql and creates 8 tables with seed data.
Local Development
wrangler d1 migrations apply bloqr-backend-admin-db --localThis creates a local SQLite file that simulates D1 for development.
Production
wrangler d1 migrations apply bloqr-backend-admin-db --remoteVerify
Check that the tables were created and seeded:
# Localwrangler d1 execute bloqr-backend-admin-db --local \ --command "SELECT role_name, display_name FROM admin_roles;"
# Remotewrangler d1 execute bloqr-backend-admin-db --remote \ --command "SELECT role_name, display_name FROM admin_roles;"Expected output:
| role_name | display_name |
|---|---|
viewer | Viewer |
editor | Editor |
super-admin | Super Admin |
3. Assign the First Super-Admin
This is a bootstrap problem: you need admin access to assign admin roles, but no one has admin access yet.
Step 1: Set Clerk Metadata
In the Clerk Dashboard (dashboard.clerk.com):
- Go to Users → find your user
- Click Edit → Public metadata
- Set:
{ "tier": "admin", "role": "admin"}This is the gate check — the middleware verifies publicMetadata.role === 'admin' before any permission evaluation.
Alternatively, use the Clerk Backend API:
# Find your user IDcurl https://api.clerk.com/v1/users?email_address=you@example.com \ -H "Authorization: Bearer $CLERK_SECRET_KEY"
# Set admin metadatacurl -X PATCH https://api.clerk.com/v1/users/{your_user_id}/metadata \ -H "Authorization: Bearer $CLERK_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"public_metadata": {"tier": "admin", "role": "admin"}}'Step 2: Assign the Super-Admin Role in ADMIN_DB
Since there are no existing admin role assignments at bootstrap, the first super-admin must be seeded out-of-band via a direct D1 insert (the API endpoint requires roles:assign permission, which nobody holds yet):
# Using Wrangler to execute a D1 query directlywrangler d1 execute bloqr-backend-admin-db --command \ "INSERT INTO admin_role_assignments (clerk_user_id, role_name, assigned_by) VALUES ('user_2yourClerkId', 'super-admin', 'bootstrap')"Or, if you prefer the Wrangler D1 interactive shell:
wrangler d1 execute bloqr-backend-admin-db --interactiveINSERT INTO admin_role_assignments (clerk_user_id, role_name, assigned_by)VALUES ('user_2yourClerkId', 'super-admin', 'bootstrap');Once you have confirmed the insert, subsequent role assignments can be made through the API (POST /admin/system/roles/assign) using your super-admin JWT.
Step 3: Verify
curl https://your-worker.workers.dev/admin/system/my-context \ -H "Authorization: Bearer $YOUR_CLERK_JWT"You should see:
{ "success": true, "context": { "clerk_user_id": "user_2yourClerkId", "role_name": "super-admin", "permissions": ["admin:read", "admin:write", "...all 27..."], "expires_at": null }}4. KV Cache Management
Role resolution results are cached in KV for performance. The admin system shares the RATE_LIMIT KV namespace with the application rate limiter, using an admin: prefix.
Cache Configuration
| Setting | Value |
|---|---|
| Namespace | RATE_LIMIT (existing KV binding in wrangler.toml) |
| Key prefix | admin:role:{clerkUserId} |
| TTL | 300 seconds (5 minutes) |
| Invalidation | Automatic on role assign/revoke |
Manual Cache Inspection
# List admin cache keyswrangler kv key list --namespace-id=$RATE_LIMIT_NS_ID --prefix="admin:role:"
# Read a specific cache entrywrangler kv get --namespace-id=$RATE_LIMIT_NS_ID "admin:role:user_2abc123"
# Manually delete a cache entry (force D1 re-lookup)wrangler kv delete --namespace-id=$RATE_LIMIT_NS_ID "admin:role:user_2abc123"Cache Staleness
If you modify a role’s permissions directly in D1 (e.g., via wrangler d1 execute), the KV cache will serve stale data for up to 5 minutes. To force immediate effect:
- Delete the affected user’s cache key (see above), or
- Wait 5 minutes for the TTL to expire.
The admin API handles cache invalidation automatically — this only applies to out-of-band D1 changes.
5. Environment Variables & Bindings
The admin system requires these bindings in wrangler.toml:
Required
| Binding | Type | Purpose |
|---|---|---|
ADMIN_DB | D1 Database | Admin configuration and audit storage |
Shared with Application
| Binding | Type | Purpose |
|---|---|---|
RATE_LIMIT | KV Namespace | Role cache (admin: prefix) |
ANALYTICS | Analytics Engine | Admin event reporting |
CLERK_PUBLISHABLE_KEY | Secret | JWT verification |
CLERK_SECRET_KEY | Secret | Backend API calls |
Optional toggles
| Binding | Type | Purpose |
|---|---|---|
VERBOSE_ERRORS | Var ('true'/'1') | Surface the error message + stack in 500 responses — but only to authenticated admins. Non-admins always receive the generic body regardless of this toggle. Absent (or any other value) = disabled, the safe default. |
VERBOSE_ERRORS is a non-secret boolean, so it belongs in [vars] (not a
secret binding). Turn it on for alpha debugging and off before wider exposure:
[vars]VERBOSE_ERRORS = "true" # admin-only detailed 500s; remove/omit to disableSecurity invariant — verbose detail requires both the toggle on and an admin auth context; a mis-set toggle can never leak internals to a normal user (
worker/utils/verbose-errors.ts, enforced at the global error boundary inworker/hono-app.ts).
wrangler.toml snippet
# Admin D1 database (uncomment after creating)[[d1_databases]]binding = "ADMIN_DB"database_name = "bloqr-backend-admin-db"database_id = "<your-database-id>"migrations_dir = "admin-migrations"
# Shared KV (already configured for rate limiting)[[kv_namespaces]]binding = "RATE_LIMIT"id = "<your-kv-namespace-id>"
# Analytics Engine (already configured)[[analytics_engine_datasets]]binding = "ANALYTICS"dataset = "bloqr_compiler_analytics"6. Ongoing Operations
Adding New Admins
As a super-admin, you can onboard new administrators:
- Have the user create a Clerk account (sign up via the web UI).
- Set their Clerk
publicMetadata.roleto"admin"(via Clerk Dashboard or API). - Assign them an appropriate admin role:
# Read-only accesscurl -X POST /admin/system/roles/assign \ -H "Authorization: Bearer $JWT" \ -d '{"clerk_user_id": "user_2newAdmin", "role_name": "viewer"}'
# Config management accesscurl -X POST /admin/system/roles/assign \ -H "Authorization: Bearer $JWT" \ -d '{"clerk_user_id": "user_2newAdmin", "role_name": "editor"}'
# Full admin access (use sparingly)curl -X POST /admin/system/roles/assign \ -H "Authorization: Bearer $JWT" \ -d '{"clerk_user_id": "user_2newAdmin", "role_name": "super-admin"}'Rotating or Revoking Access
# Revoke a rolecurl -X DELETE /admin/system/roles/revoke \ -H "Authorization: Bearer $JWT" \ -d '{"clerk_user_id": "user_2former", "role_name": "editor"}'
# Also remove Clerk admin metadata to fully revoke accesscurl -X PATCH https://api.clerk.com/v1/users/{user_id}/metadata \ -H "Authorization: Bearer $CLERK_SECRET_KEY" \ -d '{"public_metadata": {"role": null}}'Monitoring Audit Logs
Regularly review the audit log for unexpected changes:
# Check for denied access attemptscurl "/admin/system/audit?status=denied&limit=50" \ -H "Authorization: Bearer $JWT"
# Review all config changes in the last 24 hourscurl "/admin/system/audit?since=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ)&limit=100" \ -H "Authorization: Bearer $JWT"Database Maintenance
D1 is managed by Cloudflare, but you can run maintenance commands:
# Check table sizeswrangler d1 execute bloqr-backend-admin-db --remote \ --command "SELECT name, (SELECT COUNT(*) FROM admin_audit_logs) as audit_count FROM sqlite_master WHERE type='table' LIMIT 1;"
# Backup (export)wrangler d1 export bloqr-backend-admin-db --remote --output admin-backup.sqlTroubleshooting
”ADMIN_DB is not defined”
The ADMIN_DB binding is commented out in wrangler.toml. Follow step 1 above to create and configure it.
”403 Forbidden — not an admin”
The user’s Clerk publicMetadata.role is not set to "admin". Update it in the Clerk Dashboard.
”403 Forbidden — insufficient permission”
The user has an admin role but it lacks the required permission. Check their role:
curl "/admin/system/roles/assignments?clerk_user_id=user_2abc123" \ -H "Authorization: Bearer $JWT"Then check the role’s permissions:
curl /admin/system/roles -H "Authorization: Bearer $JWT"Role changes not taking effect
KV cache may be serving stale data. Either wait 5 minutes or manually invalidate:
wrangler kv delete --namespace-id=$RATE_LIMIT_NS_ID "admin:role:user_2abc123"Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.