---
title: "Add forms to Cloudflare Workers"
description: "Add a form backend to Cloudflare Workers or Pages. File uploads, validation, and email notifications included."
url: https://forminit.com/docs/cloudflare-workers/
---

# Add forms to Cloudflare Workers

Learn how to handle form submissions in Cloudflare Workers and Pages using the Forminit SDK.

## What is Forminit?

Forminit is a form backend service that handles everything you need for web forms: submissions, storage, validation, and notifications. Instead of setting up databases, building validation logic, and configuring email services, Forminit provides all of this through a simple API.

Your Cloudflare Worker acts as a secure proxy, keeping your API key hidden while forwarding form submissions to Forminit.

---

## Why Use Forminit with Cloudflare?

**Benefits for your forms:**

- **No database required** - Submissions are stored securely in Forminit's infrastructure
- **Built-in email notifications** - Get notified instantly when forms are submitted
- **Automatic validation** - Email, phone, URL, and country fields are validated server-side
- **File upload handling** - Accept files up to 25 MB without configuring storage
- **Spam protection** - Integrates with Cloudflare Turnstile, reCAPTCHA, hCaptcha, and honeypot
- **UTM tracking** - Automatically capture marketing attribution data
- **Dashboard access** - View, search, and export submissions from a web interface

**Benefits of Cloudflare Workers & Pages:**

- **Global edge deployment** - Low latency responses worldwide
- **No cold starts** - Instant execution, no waiting for containers to spin up
- **Cost effective** - Generous free tier with 100,000 requests/day
- **Simple deployment** - Deploy with `wrangler deploy` or git push

---

## Prerequisites

Before integrating Forminit with Cloudflare Workers:

1. **Create a Forminit account** at [forminit.com](https://forminit.com)
2. **Create a form** in your dashboard and copy the Form ID
3. **Create an API key** from **Account > API Tokens**
4. **Install Wrangler** (Cloudflare's CLI tool): `npm install -g wrangler`

---

## Quick Start

### 1. Create a New Worker Project

```bash
npm create cloudflare@latest forminit-worker
cd forminit-worker
```

Select "Hello World" worker when prompted.

### 2. Install the Forminit SDK

```bash
npm install forminit
```

### 3. Set Your API Key as a Secret

```bash
wrangler secret put FORMINIT_API_KEY
```

Enter your Forminit API key when prompted. This keeps your key secure and out of your code.

### 4. Set Authentication Mode to Protected

In your Forminit dashboard, go to Form Settings and set authentication mode to **Protected**. This requires the API key for submissions.

### 5. Create the Worker

Replace the contents of `src/index.ts`:

```typescript
import { Forminit } from 'forminit';

export interface Env {
  FORMINIT_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // CORS headers for browser requests
    const corsHeaders = {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    };

    // Handle preflight requests
    if (request.method === 'OPTIONS') {
      return new Response(null, { headers: corsHeaders });
    }

    // Only allow POST requests
    if (request.method !== 'POST') {
      return new Response('Method not allowed', { status: 405 });
    }

    try {
      const body = await request.json() as { formId: string; blocks: unknown[] };
      const { formId, blocks } = body;

      if (!formId) {
        return Response.json(
          { success: false, error: 'MISSING_FORM_ID', message: 'Form ID is required' },
          { status: 400, headers: corsHeaders }
        );
      }

      // Initialize the Forminit SDK
      const forminit = new Forminit({
        apiKey: env.FORMINIT_API_KEY,
      });

      // Set user info for accurate geolocation and analytics
      forminit.setUserInfo({
        ip: request.headers.get('cf-connecting-ip') || undefined,
        userAgent: request.headers.get('user-agent') || undefined,
        referer: request.headers.get('referer') || undefined,
      });

      // Submit to Forminit
      const { data, redirectUrl, error } = await forminit.submit(formId, { blocks });

      if (error) {
        return Response.json(
          { success: false, error: error.error, message: error.message, code: error.code },
          { status: error.code || 400, headers: corsHeaders }
        );
      }

      return Response.json(
        { success: true, submission: data, redirectUrl },
        { status: 200, headers: corsHeaders }
      );
    } catch (err) {
      return Response.json(
        { success: false, error: 'WORKER_ERROR', message: 'Failed to process submission' },
        { status: 500, headers: corsHeaders }
      );
    }
  },
};
```

### 6. Deploy

```bash
wrangler deploy
```

Your worker is now live at `https://forminit-worker.<your-subdomain>.workers.dev`

---

## Sending Submissions from Your Frontend

### Basic Example

```javascript
async function submitForm(formData) {
  const response = await fetch('https://forminit-worker.your-subdomain.workers.dev', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      formId: 'YOUR_FORM_ID',
      blocks: [
        {
          type: 'sender',
          properties: {
            email: formData.email,
            firstName: formData.firstName,
            lastName: formData.lastName,
          },
        },
        {
          type: 'text',
          name: 'message',
          value: formData.message,
        },
      ],
    }),
  });

  const result = await response.json();

  if (result.success) {
    console.log('Submission ID:', result.submission.hashId);
  } else {
    console.error('Error:', result.message);
  }
}
```

### HTML Form Example

```html
<form id="contact-form">
  <input type="text" id="firstName" placeholder="First name" required />
  <input type="text" id="lastName" placeholder="Last name" required />
  <input type="email" id="email" placeholder="Email" required />
  <textarea id="message" placeholder="Message" required></textarea>
  <button type="submit">Send</button>
</form>

<script>
  document.getElementById('contact-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const response = await fetch('https://your-worker.workers.dev', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        formId: 'YOUR_FORM_ID',
        blocks: [
          {
            type: 'sender',
            properties: {
              email: document.getElementById('email').value,
              firstName: document.getElementById('firstName').value,
              lastName: document.getElementById('lastName').value,
            },
          },
          {
            type: 'text',
            name: 'message',
            value: document.getElementById('message').value,
          },
        ],
      }),
    });

    const result = await response.json();
    
    if (result.success) {
      alert('Message sent!');
      e.target.reset();
    } else {
      alert('Error: ' + result.message);
    }
  });
</script>
```

---

## Handling File Uploads

For file uploads, you need to handle `multipart/form-data`. Here's an extended worker:

```typescript
import { Forminit } from 'forminit';

export interface Env {
  FORMINIT_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const corsHeaders = {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    };

    if (request.method === 'OPTIONS') {
      return new Response(null, { headers: corsHeaders });
    }

    if (request.method !== 'POST') {
      return new Response('Method not allowed', { status: 405 });
    }

    try {
      const contentType = request.headers.get('content-type') || '';
      
      const forminit = new Forminit({
        apiKey: env.FORMINIT_API_KEY,
      });

      forminit.setUserInfo({
        ip: request.headers.get('cf-connecting-ip') || undefined,
        userAgent: request.headers.get('user-agent') || undefined,
        referer: request.headers.get('referer') || undefined,
      });

      let formId: string;
      let payload: FormData | { blocks: unknown[] };

      if (contentType.includes('multipart/form-data')) {
        // Handle file uploads with FormData
        const formData = await request.formData();
        formId = formData.get('formId') as string;
        formData.delete('formId');
        payload = formData;
      } else {
        // Handle JSON submissions
        const body = await request.json() as { formId: string; blocks: unknown[] };
        formId = body.formId;
        payload = { blocks: body.blocks };
      }

      if (!formId) {
        return Response.json(
          { success: false, error: 'MISSING_FORM_ID', message: 'Form ID is required' },
          { status: 400, headers: corsHeaders }
        );
      }

      const { data, redirectUrl, error } = await forminit.submit(formId, payload);

      if (error) {
        return Response.json(
          { success: false, error: error.error, message: error.message, code: error.code },
          { status: error.code || 400, headers: corsHeaders }
        );
      }

      return Response.json(
        { success: true, submission: data, redirectUrl },
        { status: 200, headers: corsHeaders }
      );
    } catch (err) {
      return Response.json(
        { success: false, error: 'WORKER_ERROR', message: 'Failed to process submission' },
        { status: 500, headers: corsHeaders }
      );
    }
  },
};
```

---

## Response Structure

### Success Response

```json
{
  "success": true,
  "redirectUrl": "https://forminit.com/thank-you",
  "submission": {
    "hashId": "7LMIBoYY74JOCp1k",
    "date": "2026-01-17 14:30:00",
    "blocks": {
      "sender": {
        "firstName": "John",
        "lastName": "Doe",
        "email": "john@example.com"
      },
      "message": "Hello world"
    }
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` on success |
| `redirectUrl` | string | Thank you page URL |
| `submission.hashId` | string | Unique submission identifier |
| `submission.date` | string | Timestamp (YYYY-MM-DD HH:mm:ss) |
| `submission.blocks` | object | All submitted field values |

### Error Response

```json
{
  "success": false,
  "error": "FI_SCHEMA_FORMAT_EMAIL",
  "code": 400,
  "message": "Invalid email format for field: 'contact'. Please enter a valid email address."
}
```

---

## Common Error Codes

| Error Code | Status | Description |
|------------|--------|-------------|
| `FORM_NOT_FOUND` | 404 | Form ID doesn't exist |
| `FORM_DISABLED` | 403 | Form is disabled |
| `MISSING_API_KEY` | 401 | API key not provided |
| `EMPTY_SUBMISSION` | 400 | No fields submitted |
| `FI_SCHEMA_FORMAT_EMAIL` | 400 | Invalid email format |
| `FI_RULES_PHONE_INVALID` | 400 | Invalid phone format |
| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded |

---

## Form Blocks Reference

Forminit uses a block-based system to structure form data. Each submission contains an array of blocks representing different field types.

**Quick reference:**

| Block Type | Description |
|------------|-------------|
| `sender` | Submitter info (email, name, phone) - required |
| `text` | Free-form text |
| `number` | Numeric value |
| `email` | Additional email (not sender's) |
| `phone` | Phone in E.164 format |
| `select` | Single or multi-select |
| `rating` | Rating 1-5 |
| `date` | ISO 8601 date |
| `file` | File upload |
| `country` | ISO country code |

For complete documentation on all block types, validation rules, and examples, see the **[Form Blocks Reference](https://forminit.com/docs/form-blocks/)**.

---

## Security Best Practices

1. **Store API keys as secrets** - Use `wrangler secret put` instead of environment variables in `wrangler.toml`

2. **Validate input** - Check that required fields exist before forwarding to Forminit

3. **Use CORS appropriately** - Restrict `Access-Control-Allow-Origin` to your domain in production:
   ```typescript
   'Access-Control-Allow-Origin': 'https://yourdomain.com'
   ```

4. **Forward client IP** - Always include user info via `setUserInfo()` for accurate geolocation and analytics

5. **Handle errors gracefully** - Never expose internal errors to clients

---

## Environment Variables

Add these to your `wrangler.toml` for non-sensitive configuration:

```toml
name = "forminit-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"

[vars]
ALLOWED_ORIGIN = "https://yourdomain.com"
```

For your API key, always use secrets:

```bash
wrangler secret put FORMINIT_API_KEY
```

---

## Using Cloudflare Pages

If you're hosting your frontend on Cloudflare Pages, you have two options:

### Option 1: Public Mode (No Server Code)

For simple forms that don't need API key protection, you can use Forminit directly from your frontend with the CDN SDK.

**1. Set Authentication Mode to Public**

In your Forminit dashboard, go to Form Settings and set authentication mode to **Public**.

**2. Add the SDK to Your HTML**

```html
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
```

**3. Submit Forms Directly**

```html
<form id="contact-form">
  <input type="text" name="fi-sender-firstName" placeholder="First name" required />
  <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
  <input type="email" name="fi-sender-email" placeholder="Email" required />
  <textarea name="fi-text-message" placeholder="Message" required></textarea>
  <button type="submit">Send</button>
</form>

<p id="form-result"></p>

<script>
  const forminit = new Forminit();
  const FORM_ID = 'YOUR_FORM_ID';

  document.getElementById('contact-form').addEventListener('submit', async (e) => {
    e.preventDefault();

    const formData = new FormData(e.target);
    const { data, error } = await forminit.submit(FORM_ID, formData);

    if (error) {
      document.getElementById('form-result').textContent = error.message;
      return;
    }

    document.getElementById('form-result').textContent = 'Message sent!';
    e.target.reset();
  });
</script>
```

This approach is simpler and works great for basic contact forms.

### Option 2: Pages Functions (Protected Mode)

If you need to keep your API key secure, use Pages Functions. These are Workers that run alongside your Pages site.

**1. Create the Function**

Create `functions/api/forminit.ts` in your Pages project:

```typescript
import { Forminit } from 'forminit';

interface Env {
  FORMINIT_API_KEY: string;
}

export const onRequestPost: PagesFunction<Env> = async (context) => {
  const { request, env } = context;

  const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
  };

  try {
    const body = await request.json() as { formId: string; blocks: unknown[] };
    const { formId, blocks } = body;

    if (!formId) {
      return Response.json(
        { success: false, error: 'MISSING_FORM_ID', message: 'Form ID is required' },
        { status: 400, headers: corsHeaders }
      );
    }

    const forminit = new Forminit({
      apiKey: env.FORMINIT_API_KEY,
    });

    forminit.setUserInfo({
      ip: request.headers.get('cf-connecting-ip') || undefined,
      userAgent: request.headers.get('user-agent') || undefined,
      referer: request.headers.get('referer') || undefined,
    });

    const { data, redirectUrl, error } = await forminit.submit(formId, { blocks });

    if (error) {
      return Response.json(
        { success: false, error: error.error, message: error.message, code: error.code },
        { status: error.code || 400, headers: corsHeaders }
      );
    }

    return Response.json(
      { success: true, submission: data, redirectUrl },
      { status: 200, headers: corsHeaders }
    );
  } catch (err) {
    return Response.json(
      { success: false, error: 'WORKER_ERROR', message: 'Failed to process submission' },
      { status: 500, headers: corsHeaders }
    );
  }
};

export const onRequestOptions: PagesFunction = async () => {
  return new Response(null, {
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    },
  });
};
```

**2. Install the SDK**

```bash
npm install forminit
```

**3. Add Your API Key**

In the Cloudflare dashboard, go to your Pages project > Settings > Environment variables and add:

- Variable name: `FORMINIT_API_KEY`
- Value: Your Forminit API key
- Select "Encrypt" to keep it secure

**4. Submit from Your Frontend**

```javascript
const response = await fetch('/api/forminit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    formId: 'YOUR_FORM_ID',
    blocks: [
      {
        type: 'sender',
        properties: {
          email: 'john@example.com',
          firstName: 'John',
          lastName: 'Doe',
        },
      },
      {
        type: 'text',
        name: 'message',
        value: 'Hello world',
      },
    ],
  }),
});

const result = await response.json();
```

---

## Related Documentation

- [Form Blocks Reference](https://forminit.com/docs/form-blocks/) - Complete reference for all block types
- [File Uploads](https://forminit.com/docs/file-upload/) - Detailed file upload guide
- [Submit Form API](https://forminit.com/docs/submit-form-api/) - Full REST API documentation
- [Node.js Integration](https://forminit.com/docs/nodejs/) - Node.js setup guide
