Skip to content

Handle form submissions with Flask

Learn how to handle form submissions in Flask 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 flask 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-flask-app/
├── app.py
├── .env
└── templates/
    └── contact.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.

# app.py
import os
from flask import Flask, request, jsonify, render_template
from forminit import ForminitClient
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
FORM_ID = "YOUR_FORM_ID"


@app.route("/")
def index():
    return render_template("contact.html")


@app.route("/submit", methods=["POST"])
def submit():
    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    # Pass user info for geolocation and attribution tracking
    client.set_user_info(
        ip=request.headers.get("X-Forwarded-For", request.remote_addr),
        user_agent=request.headers.get("User-Agent"),
        referer=request.headers.get("Referer"),
    )

    # Submit flat form data from HTML form
    form_data = request.form.to_dict()
    response = client.submit(FORM_ID, form_data)
    client.close()

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

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


if __name__ == "__main__":
    app.run(debug=True)
<!-- templates/contact.html -->
<form action="/submit" method="POST">
  <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 TypePatternExample
Sender propertiesfi-sender-{property}fi-sender-email
Field blocksfi-{type}-{name}fi-text-message
flask run

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

@app.route("/api/direct-submit", methods=["POST"])
def direct_submit():
    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    client.set_user_info(
        ip=request.headers.get("X-Forwarded-For", request.remote_addr),
        user_agent=request.headers.get("User-Agent"),
        referer=request.headers.get("Referer"),
    )

    payload = request.get_json()
    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 jsonify({"success": False, "message": response["error"]["message"]}), 400

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

Pass UTM parameters and ad click IDs via the tracking argument:

@app.route("/submit", methods=["POST"])
def submit():
    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    client.set_user_info(
        ip=request.headers.get("X-Forwarded-For", request.remote_addr),
        user_agent=request.headers.get("User-Agent"),
        referer=request.headers.get("Referer"),
    )

    form_data = request.form.to_dict()

    # Extract tracking parameters from query string
    tracking = {}
    for param in ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", "gclid", "fbclid"]:
        value = request.args.get(param)
        if value:
            tracking[param] = value

    response = client.submit(FORM_ID, form_data, tracking=tracking)
    client.close()

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

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

const { data, redirectUrl, error } = response

# data contains:
{
    "hashId": "7LMIBoYY74JOCp1k",
    "date": "2026-01-01 21:10:24",
    "blocks": {
        "sender": {
            "firstName": "John",
            "lastName": "Doe",
            "email": "john@example.com"
        },
        "message": "Hello world"
    }
}
FieldTypeDescription
data.hashIdstrUnique submission identifier
data.datestrSubmission timestamp (YYYY-MM-DD HH:mm:ss)
data.blocksdictAll submitted field values
redirectUrlstrThank you page URL
{
    "error": {
        "error": "FI_SCHEMA_FORMAT_EMAIL",
        "code": 400,
        "message": "Invalid email format"
    }
}

@app.route("/submit", methods=["POST"])
def submit():
    client = ForminitClient(api_key=os.environ["FORMINIT_API_KEY"])

    client.set_user_info(
        ip=request.headers.get("X-Forwarded-For", request.remote_addr),
        user_agent=request.headers.get("User-Agent"),
        referer=request.headers.get("Referer"),
    )

    form_data = request.form.to_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 jsonify({"success": False, "field": "email", "message": "Invalid email address"}), 400
        elif error_code == "FI_RULES_PHONE_INVALID":
            return jsonify({"success": False, "field": "phone", "message": "Invalid phone number"}), 400
        elif error_code == "TOO_MANY_REQUESTS":
            return jsonify({"success": False, "message": "Please wait before submitting again"}), 429
        else:
            return jsonify({"success": False, "message": error.get("message")}), 400

    return jsonify({"success": True, "submissionId": response["data"]["hashId"]})
Error CodeHTTP StatusDescription
FORM_NOT_FOUND404Form ID doesn’t exist or was deleted
FORM_DISABLED403Form is disabled by owner
MISSING_API_KEY401API key required but not provided
EMPTY_SUBMISSION400No fields with values submitted
FI_SCHEMA_FORMAT_EMAIL400Invalid email format
FI_RULES_PHONE_INVALID400Invalid phone number format
FI_SCHEMA_RANGE_RATING400Rating not between 1-5
FI_DATA_COUNTRY_INVALID400Invalid country code
TOO_MANY_REQUESTS429Rate 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. Validate input server-side — Don’t rely solely on client-side validation
  4. Use HTTPS — Always use secure connections in production
  5. Call set_user_info() — Pass the real user’s IP, user agent, and referer for accurate tracking