Deploy
Conveyor deploys as a single Worker with a D1 database. You need:
- A Cloudflare account.
- Node.js 22 or newer.
- Wrangler, signed in to your account (
npx wrangler login).
1. Install
git clone <repository-url> conveyorcd conveyornpm install2. Create the database
cd apps/workernpx wrangler d1 create conveyorCopy the printed database_id into the d1_databases entry in apps/worker/wrangler.jsonc,
replacing the placeholder value.
3. Set the secrets
npx wrangler secret put AUTH_SECRETnpx wrangler secret put SETUP_TOKENAUTH_SECRETsigns session cookies and share tokens. Use at least 32 random bytes, for example the output ofopenssl rand -base64 48. Rotating it logs everyone out.SETUP_TOKENprotects the creation of the first admin. Use a long random value, for example the output ofopenssl rand -base64 24. Both secrets are required:wrangler deployrefuses to deploy without them.
4. Deploy
From the repository root:
npm run deployThis builds the dashboard and the tracker, applies the D1 migrations to the remote database and deploys the Worker.
5. Create the first admin
Open https://<your-domain>/setup and enter your SETUP_TOKEN along with the username and
password for the first admin. You can also open https://<your-domain>/setup?token=<SETUP_TOKEN>
to have the token field prefilled. Setup is only available while no user exists, and a wrong token
is rejected.
SETUP_TOKEN is only used for this first run. You can rotate or delete it afterwards.
Then add your website in the dashboard and copy its tracking tag into your pages.
Use a custom domain
Serve Conveyor from a custom domain or route rather than *.workers.dev. Conveyor uses the Cache
API to recognise visits for 30 minutes, and the Cache API does nothing on workers.dev. There,
Conveyor falls back to fixed 30-minute windows, so a visit that crosses a window boundary counts
as two.
Protect the tracking endpoint
POST /api/send has to be public, because visitors’ browsers call it from your sites. Anyone can
call it, and every call counts as a Worker request. On the Workers Free plan the worst case is that
someone uses up the 100,000 requests for the day. You are never billed: tracking and the dashboard
API stop answering until the quota resets at 00:00 UTC. On a paid plan the extra requests are billed.
Conveyor already drops oversized bodies, unknown website ids, events from hostnames that don’t match the website’s domain, and known bots. Those requests still count, though, because the Worker has to run to drop them.
A rate-limiting rule in Cloudflare’s WAF (Web Application Firewall) stops a flood before the
Worker runs, so blocked requests are not counted. The Free plan includes one rate-limiting rule,
and it needs a custom domain: WAF rules belong to your zone, and workers.dev is not your zone.
In the Cloudflare dashboard, open your domain, go to Security → WAF → Rate limiting rules and create a rule:
| Setting | Value |
|---|---|
| Rule name | Limit tracker ingest |
| If incoming requests match | Hostname equals analytics.example.com and URI Path equals /api/send |
| With the same | IP |
| When rate exceeds | 30 requests per 10 seconds |
| Then take action | Block |
| For duration | 10 seconds |
Replace the hostname with the one your instance runs on. A real visitor sends a handful of events per page, so 30 in 10 seconds only catches scripts. The tracker ignores failed requests, so a blocked visitor’s pages keep working.
Don’t add challenges (Managed Challenge, JS Challenge, Bot Fight Mode) to /api/send. The tracker
calls it in the background, where a visitor can’t solve a challenge, so tracking would silently
stop.
Data retention
Raw events are deleted after RAW_RETENTION_DAYS days (90 by default, set under vars in
wrangler.jsonc). Daily rollups are kept, so long-range charts keep working. A scheduled job runs
hourly to rotate salts, roll up completed days and prune old events.