---
title: "Add forms to Django"
description: "Add a form backend to your Django application. Forminit handles submissions, validation, file uploads, and notifications."
url: https://forminit.com/docs/django/
---

# Add forms to Django

Learn how to handle form submissions in Django using the Forminit Python SDK.

---

## Prerequisites

1. **Create a Forminit account** at [forminit.com](https://forminit.com)
2. **Create a form** in your dashboard
3. **Set authentication mode to Protected** in Form Settings
4. **Create an API token** from **Account → API Tokens**

---

## 1. Install Dependencies

```bash
pip install forminit django python-dotenv
```

## 2. Set Authentication Mode to Protected

Set authentication to Protected for server-side integrations. This enables higher rate limits and requires the `x-api-key` header.

<img src="/docs/auth-mode-protected.png" alt="Forminit Authentication Mode Protected" style="width: 450px;" />

## 3. Create an API Token and Add to Environment

Generate your secret API token from **Account → API Tokens** in the Forminit dashboard.

```bash
# .env
FORMINIT_API_KEY="fi_your_secret_api_key"
```

> **Caution**
>
> Never commit API keys to version control. Add `.env` to your `.gitignore` file.

## 4. Project Structure

```
my-django-app/
├── manage.py
├── .env
├── mysite/
│   ├── settings.py
│   └── urls.py
└── contact/
    ├── views.py
    ├── urls.py
    └── templates/
        └── contact/
            └── index.html
```

## 5. Create the View

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

For complete documentation on all available blocks, field naming conventions, and validation rules, see the [Form Blocks Reference](https://forminit.com/docs/form-blocks/).

```python
# contact/views.py
import os
from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from forminit import ForminitClient
from dotenv import load_dotenv

load_dotenv()

FORM_ID = "YOUR_FORM_ID"

def index(request):
    return render(request, "contact/index.html")

@csrf_exempt
def submit(request):
    if request.method != "POST":
        return JsonResponse({"error": "Method not allowed"}, status=405)

    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    # Extract user info from Django request
    user_ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR"))
    user_agent = request.META.get("HTTP_USER_AGENT", "")
    referer = request.META.get("HTTP_REFERER")

    client.set_user_info(ip=user_ip, user_agent=user_agent, referer=referer)

    # Submit flat form data
    form_data = request.POST.dict()
    response = client.submit(FORM_ID, form_data)
    client.close()

    if response.get("error"):
        return JsonResponse(
            {"success": False, "message": response["error"]["message"]}, status=400
        )

    return JsonResponse({"success": True, "submissionId": response["data"]["hashId"]})
```

## 6. Configure URLs

```python
# contact/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("submit/", views.submit, name="submit"),
]
```

```python
# mysite/urls.py
from django.urls import path, include

urlpatterns = [
    path("", include("contact.urls")),
]
```

## 7. Create the HTML Template

```html
<!-- contact/templates/contact/index.html -->
<form action="{% url 'submit' %}" method="POST">
  {% csrf_token %}
  <input type="text" name="fi-sender-fullName" placeholder="Name" required />
  <input type="email" name="fi-sender-email" placeholder="Email" required />
  <input type="tel" name="fi-sender-phone" placeholder="Phone" />
  <textarea name="fi-text-message" placeholder="Message" required></textarea>
  <button type="submit">Send</button>
</form>
```

**Field Naming Patterns:**

| Block Type | Pattern | Example |
|------------|---------|---------|
| Sender properties | `fi-sender-{property}` | `fi-sender-email` |
| Field blocks | `fi-{type}-{name}` | `fi-text-message` |

## 8. Run

```bash
python manage.py runserver
```

---

## JSON Submission with Structured Blocks

For more control over the request payload, use JSON format with structured blocks:

```python
# contact/views.py
import json
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def direct_submit(request):
    if request.method != "POST":
        return JsonResponse({"error": "Method not allowed"}, status=405)

    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    user_ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR"))
    user_agent = request.META.get("HTTP_USER_AGENT", "")
    referer = request.META.get("HTTP_REFERER")

    client.set_user_info(ip=user_ip, user_agent=user_agent, referer=referer)

    try:
        payload = json.loads(request.body)
    except json.JSONDecodeError:
        return JsonResponse({"error": "Invalid JSON"}, status=400)

    response = client.submit(FORM_ID, {
        "blocks": [
            {
                "type": "sender",
                "properties": {
                    "email": payload.get("email"),
                    "firstName": payload.get("firstName"),
                    "lastName": payload.get("lastName"),
                },
            },
            {
                "type": "text",
                "name": "message",
                "value": payload.get("message"),
            },
            {
                "type": "select",
                "name": "plan",
                "value": payload.get("plan"),
            },
        ],
    })
    client.close()

    if response.get("error"):
        return JsonResponse(
            {"success": False, "message": response["error"]["message"]}, status=400
        )

    return JsonResponse({"success": True, "submissionId": response["data"]["hashId"]})
```

Add the URL:

```python
# contact/urls.py
urlpatterns = [
    path("", views.index, name="index"),
    path("submit/", views.submit, name="submit"),
    path("api/direct-submit/", views.direct_submit, name="direct_submit"),
]
```

> **Caution**
>
> JSON submissions do not support file uploads. Use flat form data for forms with file fields.

---

## Django User Info Extraction

Django stores HTTP headers in `request.META` with an `HTTP_` prefix. Here's how to extract user info for Forminit tracking:

```python
# IP address (supports proxy headers)
user_ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR"))

# User agent
user_agent = request.META.get("HTTP_USER_AGENT", "")

# Referer
referer = request.META.get("HTTP_REFERER")

client.set_user_info(ip=user_ip, user_agent=user_agent, referer=referer)
```

---

## Response Structure

### Success Response

```python
{
    "data": {
        "hashId": "7LMIBoYY74JOCp1k",
        "date": "2026-01-01 21:10:24",
        "blocks": {
            "sender": {
                "firstName": "John",
                "lastName": "Doe",
                "email": "john@example.com"
            },
            "message": "Hello world"
        }
    },
    "redirectUrl": "https://forminit.com/thank-you"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `data.hashId` | str | Unique submission identifier |
| `data.date` | str | Submission timestamp (YYYY-MM-DD HH:mm:ss) |
| `data.blocks` | dict | All submitted field values |
| `redirectUrl` | str | Thank you page URL |

### Error Response

```python
{
    "error": {
        "error": "FI_SCHEMA_FORMAT_EMAIL",
        "code": 400,
        "message": "Invalid email format"
    }
}
```

---

## Error Handling

```python
@csrf_exempt
def submit(request):
    if request.method != "POST":
        return JsonResponse({"error": "Method not allowed"}, status=405)

    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    user_ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META.get("REMOTE_ADDR"))
    user_agent = request.META.get("HTTP_USER_AGENT", "")
    referer = request.META.get("HTTP_REFERER")

    client.set_user_info(ip=user_ip, user_agent=user_agent, referer=referer)

    form_data = request.POST.dict()
    response = client.submit(FORM_ID, form_data)
    client.close()

    if response.get("error"):
        error = response["error"]
        error_code = error.get("error")

        if error_code == "FI_SCHEMA_FORMAT_EMAIL":
            return JsonResponse({"success": False, "field": "email", "message": "Invalid email address"}, status=400)
        elif error_code == "FI_RULES_PHONE_INVALID":
            return JsonResponse({"success": False, "field": "phone", "message": "Invalid phone number"}, status=400)
        elif error_code == "TOO_MANY_REQUESTS":
            return JsonResponse({"success": False, "message": "Please wait before submitting again"}, status=429)
        else:
            return JsonResponse({"success": False, "message": error.get("message")}, status=400)

    return JsonResponse({"success": True, "submissionId": response["data"]["hashId"]})
```

### Common Error Codes

| Error Code | HTTP Status | Description |
|------------|-------------|-------------|
| `FORM_NOT_FOUND` | 404 | Form ID doesn't exist or was deleted |
| `FORM_DISABLED` | 403 | Form is disabled by owner |
| `MISSING_API_KEY` | 401 | API key required but not provided |
| `EMPTY_SUBMISSION` | 400 | No fields with values submitted |
| `FI_SCHEMA_FORMAT_EMAIL` | 400 | Invalid email format |
| `FI_RULES_PHONE_INVALID` | 400 | Invalid phone number format |
| `FI_SCHEMA_RANGE_RATING` | 400 | Rating not between 1-5 |
| `FI_DATA_COUNTRY_INVALID` | 400 | Invalid country code |
| `TOO_MANY_REQUESTS` | 429 | Rate limit exceeded |

---

## Security Best Practices

1. **Store API keys in environment variables** — Never hardcode keys in source code
2. **Use `.gitignore`** — Exclude `.env` files from version control
3. **Use `@csrf_exempt` carefully** — Only on API endpoints that need it
4. **Validate input server-side** — Don't rely solely on client-side validation
5. **Use HTTPS** — Always use secure connections in production
6. **Call `set_user_info()`** — Pass the real user's IP, user agent, and referer for accurate tracking

---

## Related Documentation

- [Python SDK Reference](https://forminit.com/docs/python-sdk/) — Full API reference for the Python SDK
- [Python Introduction](https://forminit.com/docs/python/) — General Python setup and usage
- [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
- [API Reference](https://forminit.com/docs/submit-form-api/) — Full REST API documentation
