Handle form submissions with Flask
Learn how to handle form submissions in Flask using the Forminit Python SDK.
Prerequisites
Section titled “Prerequisites”- Create a Forminit account at forminit.com
- Create a form in your dashboard
- Set authentication mode to Protected in Form Settings
- Create an API token from Account → API Tokens
1. Install Dependencies
Section titled “1. Install Dependencies”pip install forminit flask python-dotenv
2. Set Authentication Mode to Protected
Section titled “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.
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"
4. Project Structure
Section titled “4. Project Structure”my-flask-app/
├── app.py
├── .env
└── templates/
└── contact.html
5. Create the Flask App
Section titled “5. Create the Flask App”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)
6. Create the HTML Template
Section titled “6. Create the HTML Template”<!-- 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 Type | Pattern | Example |
|---|---|---|
| Sender properties | fi-sender-{property} | fi-sender-email |
| Field blocks | fi-{type}-{name} | fi-text-message |
7. Run
Section titled “7. Run”flask run
JSON Submission with Structured Blocks
Section titled “JSON Submission with Structured Blocks”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"]})
With Tracking Parameters
Section titled “With Tracking Parameters”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"]})
Response Structure
Section titled “Response Structure”Success Response
Section titled “Success Response”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"
}
}
| 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
Section titled “Error Response”{
"error": {
"error": "FI_SCHEMA_FORMAT_EMAIL",
"code": 400,
"message": "Invalid email format"
}
}
Error Handling
Section titled “Error Handling”@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"]})
Common Error Codes
Section titled “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
Section titled “Security Best Practices”- Store API keys in environment variables — Never hardcode keys in source code
- Use
.gitignore— Exclude.envfiles from version control - Validate input server-side — Don’t rely solely on client-side validation
- Use HTTPS — Always use secure connections in production
- Call
set_user_info()— Pass the real user’s IP, user agent, and referer for accurate tracking
Related Documentation
Section titled “Related Documentation”- Python SDK Reference — Full API reference for the Python SDK
- Python Introduction — General Python setup and usage
- Form Blocks Reference — Complete reference for all block types
- File Uploads — Detailed file upload guide
- API Reference — Full REST API documentation
Was this page helpful?
Thanks for your feedback.