Deployment Guide

Step-by-step instructions to deploy your own instance of formdata.dev on Cloudflare.

Step 1 — Clone the Repository

git clone https://github.com/nicoswan/formdata.dev.git
cd formdata.dev

Step 2 — Install Dependencies

npm install

Step 3 — Authenticate with Cloudflare

wrangler login

This opens a browser window to authorize Wrangler with your Cloudflare account.

Step 4 — Create the D1 Database

wrangler d1 create formdata-db

Copy the database_id from the output. You will need it for Step 7.

Step 5 — Create the KV Namespace

wrangler kv namespace create FORM_KV

Copy the id from the output.

Step 6 — Create the Queues

wrangler queues create formdata-delivery
wrangler queues create formdata-delivery-dlq

Step 7 — Update wrangler.jsonc

Open wrangler.jsonc and replace the resource IDs with the ones from the previous steps:

{
  "name": "formdata-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-02-24",
  "compatibility_flags": ["nodejs_compat"],
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "formdata-db",
      "database_id": "YOUR_D1_DATABASE_ID",
      "migrations_dir": "migrations"
    }
  ],
  "kv_namespaces": [
    {
      "binding": "FORM_KV",
      "id": "YOUR_KV_NAMESPACE_ID"
    }
  ],
  "queues": {
    "producers": [
      {
        "binding": "DELIVERY_QUEUE",
        "queue": "formdata-delivery"
      }
    ],
    "consumers": [
      {
        "queue": "formdata-delivery",
        "max_batch_size": 10,
        "max_retries": 5,
        "dead_letter_queue": "formdata-delivery-dlq",
        "retry_delay": 30
      }
    ]
  },
  "durable_objects": {
    "bindings": [
      {
        "name": "MCP_AGENT",
        "class_name": "FormDataMcpAgent"
      }
    ]
  },
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": ["FormDataMcpAgent"]
    }
  ]
}

Step 8 — Run D1 Migrations

wrangler d1 migrations apply formdata-db --remote

This creates the organizations, organization_api_keys, forms, and destinations tables along with their indexes.

Step 9 — Set Secrets

If you want subdomain provisioning (custom hostnames), set these secrets:

wrangler secret put CF_ZONE_ID
# Paste your Cloudflare Zone ID when prompted

wrangler secret put CF_API_TOKEN
# Paste a Cloudflare API token with Zone > Custom Hostnames > Edit permission
Optional Secrets

CF_ZONE_ID and CF_API_TOKEN are only required for the subdomain provisioning feature. If you skip them, account creation still works — it just will not provision <slug>.yourdomain.com hostnames automatically.

Step 10 — Type Check

npm run typecheck

Verify there are no TypeScript errors before deploying.

Step 11 — Deploy

npm run deploy

Wrangler will output your Worker's URL:

https://formdata-worker.your-account.workers.dev

Step 12 — Add a Custom Domain (Optional)

  1. Go to Cloudflare Dashboard > Workers & Pages > formdata-worker > Settings > Domains & Routes
  2. Click Add Custom Domain
  3. Enter your domain (e.g., api.yourdomain.com)
  4. If there is an existing A or CNAME record for that subdomain, delete it first
  5. Cloudflare will create the required DNS record automatically

Step 13 — Verify

Create a test account to confirm everything is working:

curl -X POST https://YOUR_WORKER_URL/v1/admin/accounts \
  -H "Content-Type: application/json" \
  -d '{
    "organizationName": "Test Org",
    "ownerEmail": "[email protected]",
    "slug": "test"
  }'

You should receive a response with ok: true, an account object, and a tenantKey.

Then create a form and test the ingestion endpoint:

# Create a form (replace TENANT_KEY with the tenantKey from above)
curl -X POST https://YOUR_WORKER_URL/v1/admin/forms \
  -H "Content-Type: application/json" \
  -H "x-tenant-key: TENANT_KEY" \
  -d '{
    "name": "Test Form",
    "slug": "test-form"
  }'

# Submit to the form (replace PUBLIC_KEY with the publicKey from above)
curl -X POST https://YOUR_WORKER_URL/v1/f/PUBLIC_KEY \
  -H "Content-Type: application/json" \
  -d '{"message": "Hello from self-hosted formdata.dev!"}'
Expected Response

The submission should return 202 with ok: true. If no destinations are configured, queuedDestinations will be 0 — that is expected.

Updating

To deploy a new version after pulling changes:

git pull
npm install
wrangler d1 migrations apply formdata-db --remote
npm run deploy