Skip to content

Add forms to Django

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


  1. Create a Forminit account at 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

pip install forminit django python-dotenv

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

Forminit Authentication Mode Protected

3. Create an API Token and Add to Environment

Section titled “3. Create an API Token and Add to Environment”

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

# .env
FORMINIT_API_KEY="fi_your_secret_api_key"
my-django-app/
├── manage.py
├── .env
├── mysite/
│   ├── settings.py
│   └── urls.py
└── contact/
    ├── views.py
    ├── urls.py
    └── templates/
        └── contact/
            └── index.html

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.

# 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"]})
# contact/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("submit/", views.submit, name="submit"),
]
# mysite/urls.py
from django.urls import path, include

urlpatterns = [
    path("", include("contact.urls")),
]
<!-- 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
python manage.py runserver

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

# 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:

# 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"),
]

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

# 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)

{
    "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": {
        "error": "FI_SCHEMA_FORMAT_EMAIL",
        "code": 400,
        "message": "Invalid email format"
    }
}

@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"]})
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

  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

View this page as Markdown