Form blocks
Form blocks are the typed building blocks of every Forminit submission. Combine them to capture exactly what your form needs: contact details, file uploads, ratings, choices, anything.
- Server-side validation, no setup.
email,phone,url,country,rating, anddateare validated automatically. Bad data never reaches your dashboard or webhooks. - A consistent schema for every submission. Every block is typed, so CSV exports, webhooks, and integrations always get the same shape. No garbage data breaking automations.
senderandtrackingare built in. Usesenderto identify who submitted the form.trackingis auto-captured by the JS SDK so you always know where the visitor came from.- Compose anything. Up to 50 blocks per submission, named however you like with the pattern
fi-{blockType}-{name}. Same backend whether you’re collecting a contact request, a 12-question survey, or a file-upload portal.

All blocks in action
Section titled “All blocks in action”Every input name follows the same pattern: fi-{blockType}-{name}. That’s the only rule.
The example below packs every block type into a single submission, shown four ways so you can grab whichever fits your stack: HTML loads the SDK from the CDN (browser, no API key needed), Node.js posts plain fetch with an X-API-KEY header (server, no SDK), React uses the npm SDK inside a component, and Python uses the official Python SDK. Each tab is independently runnable. Most forms only need a handful of these blocks, so copy the tab you want and delete the ones that don’t apply to your form.
<!-- Load the SDK from the CDN -->
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<form id="contact-form">
<input type="text" name="fi-sender-firstName" placeholder="First name" />
<input type="text" name="fi-sender-lastName" placeholder="Last name" />
<!-- Single-field alternative to firstName/lastName -->
<input type="text" name="fi-sender-fullName" placeholder="Full name" />
<input type="email" name="fi-sender-email" placeholder="Email" />
<input type="text" name="fi-sender-phone" placeholder="Phone" />
<input type="text" name="fi-sender-title" placeholder="Title (Mr., Ms., Dr.)" />
<input type="text" name="fi-sender-company" placeholder="Company" />
<input type="text" name="fi-sender-position" placeholder="Job title" />
<input type="text" name="fi-sender-address" placeholder="Address" />
<input type="text" name="fi-sender-address2" placeholder="Address line 2 (apt, suite, etc.)" />
<input type="text" name="fi-sender-city" placeholder="City" />
<input type="text" name="fi-sender-postcode" placeholder="Postcode / ZIP" />
<input type="text" name="fi-sender-country" placeholder="Country (ISO 3166-1 alpha-2, e.g. US)" />
<input type="hidden" name="fi-sender-userId" value="ext_12345" />
<!-- text: free-form answer, no validation -->
<textarea name="fi-text-message" placeholder="Message"></textarea>
<!-- number: any numeric value -->
<input type="number" name="fi-number-budget" placeholder="Budget" />
<!-- email: a second email beyond the sender's -->
<input type="email" name="fi-email-invitee" placeholder="Invitee email" />
<!-- phone: a second phone beyond the sender's, E.164 format -->
<input type="text" name="fi-phone-emergency" placeholder="Emergency contact" />
<!-- url: validated as a URL -->
<input type="url" name="fi-url-website" placeholder="Your website" />
<!-- select: single or multi choice -->
<select name="fi-select-plan">
<option value="basic">Basic</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</select>
<!-- radio: single choice from a group -->
<label><input type="radio" name="fi-radio-priority" value="low" /> Low</label>
<label><input type="radio" name="fi-radio-priority" value="high" /> High</label>
<!-- checkbox: one or more choices, same name = array -->
<label><input type="checkbox" name="fi-checkbox-features" value="api" /> API</label>
<label><input type="checkbox" name="fi-checkbox-features" value="support" /> Support</label>
<!-- rating: integer between 1 and 5 -->
<input type="range" name="fi-rating-satisfaction" min="1" max="5" step="1" value="3" />
<!-- date: ISO 8601 date or date-time -->
<input type="date" name="fi-date-appointment" />
<!-- file: requires multipart/form-data (FormData uses it by default) -->
<input type="file" name="fi-file-resume" />
<!-- country: ISO 3166-1 alpha-2 code. Full list: https://gist.github.com/ssskip/5a94bfcd2835bf1dea52 -->
<select name="fi-country-shipping">
<option value="US">United States</option>
<option value="GB">United Kingdom</option>
</select>
<!-- tracking: auto-captured by the SDK from URL params and referrer -->
<button type="submit">Send</button>
<p id="form-status"></p>
</form>
<script>
const forminit = new Forminit();
const form = document.getElementById('contact-form');
const status = document.getElementById('form-status');
form.addEventListener('submit', async (e) => {
e.preventDefault();
status.textContent = '';
const { data, error } = await forminit.submit('YOUR_FORM_ID', new FormData(form));
if (error) {
status.textContent = 'Error: ' + error.message;
return;
}
status.textContent = 'Thanks! Your submission was received.';
form.reset();
});
</script> // Plain fetch with X-API-KEY, no SDK
const res = await fetch('https://forminit.com/f/YOUR_FORM_ID', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': process.env.FORMINIT_API_KEY,
},
body: JSON.stringify({
blocks: [
// sender: who's submitting (built-in, max 1 per submission)
{
type: 'sender',
properties: {
firstName: 'Jane',
lastName: 'Doe',
fullName: 'Jane Doe', // alternative to firstName/lastName
email: 'jane@example.com',
phone: '+12025550123', // E.164 format
title: 'Ms.',
company: 'Acme Corp',
position: 'Software Engineer',
address: '350 Fifth Avenue',
address2: 'Suite 200',
city: 'New York',
postcode: '10118',
country: 'US', // ISO 3166-1 alpha-2
userId: 'ext_12345', // your own user identifier
},
},
// tracking: marketing attribution (built-in, max 1; set manually here since no SDK)
{
type: 'tracking',
properties: {
utmSource: 'google',
utmMedium: 'cpc',
utmCampaign: 'summer_sale',
referrer: 'https://google.com/search',
},
},
// text: free-form answer, no validation
{ type: 'text', name: 'message', value: 'Hello from Forminit!' },
// number: any numeric value
{ type: 'number', name: 'budget', value: 5000 },
// email: a second email beyond the sender's
{ type: 'email', name: 'invitee', value: 'colleague@example.com' },
// phone: a second phone beyond the sender's, E.164 format
{ type: 'phone', name: 'emergency', value: '+14155550123' },
// url: validated as a URL
{ type: 'url', name: 'website', value: 'https://janedoe.com' },
// select: single value or array of values
{ type: 'select', name: 'plan', value: 'Pro' },
// radio: single choice
{ type: 'radio', name: 'priority', value: 'high' },
// checkbox: array for multiple selections
{ type: 'checkbox', name: 'features', value: ['api', 'support'] },
// rating: integer between 1 and 5
{ type: 'rating', name: 'satisfaction', value: 5 },
// date: ISO 8601 date or date-time
{ type: 'date', name: 'appointment', value: '2025-01-15' },
// file: NOT supported in JSON. Use multipart/form-data for uploads
// country: ISO 3166-1 alpha-2 code. Full list: https://gist.github.com/ssskip/5a94bfcd2835bf1dea52
{ type: 'country', name: 'shipping', value: 'GB' },
],
}),
});
const { data, error } = await res.json();
if (error) return console.error(error.message);
alert('Submitted! ID: ' + data.hashId); // npm install forminit
import { useState, FormEvent } from 'react';
import { Forminit } from 'forminit';
const forminit = new Forminit();
export function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('loading');
const formData = new FormData(e.currentTarget);
const { data, error } = await forminit.submit('YOUR_FORM_ID', formData);
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
alert('Submitted! ID: ' + data.hashId);
e.currentTarget.reset();
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="fi-sender-firstName" placeholder="First name" />
<input type="text" name="fi-sender-lastName" placeholder="Last name" />
{/* Single-field alternative to firstName/lastName */}
<input type="text" name="fi-sender-fullName" placeholder="Full name" />
<input type="email" name="fi-sender-email" placeholder="Email" />
<input type="text" name="fi-sender-phone" placeholder="Phone" />
<input type="text" name="fi-sender-title" placeholder="Title (Mr., Ms., Dr.)" />
<input type="text" name="fi-sender-company" placeholder="Company" />
<input type="text" name="fi-sender-position" placeholder="Job title" />
<input type="text" name="fi-sender-address" placeholder="Address" />
<input type="text" name="fi-sender-address2" placeholder="Address line 2 (apt, suite, etc.)" />
<input type="text" name="fi-sender-city" placeholder="City" />
<input type="text" name="fi-sender-postcode" placeholder="Postcode / ZIP" />
<input type="text" name="fi-sender-country" placeholder="Country (ISO 3166-1 alpha-2, e.g. US)" />
<input type="hidden" name="fi-sender-userId" value="ext_12345" />
{/* text: free-form answer, no validation */}
<textarea name="fi-text-message" placeholder="Message" />
{/* number: any numeric value */}
<input type="number" name="fi-number-budget" placeholder="Budget" />
{/* email: a second email beyond the sender's */}
<input type="email" name="fi-email-invitee" placeholder="Invitee email" />
{/* phone: a second phone beyond the sender's, E.164 format */}
<input type="text" name="fi-phone-emergency" placeholder="Emergency contact" />
{/* url: validated as a URL */}
<input type="url" name="fi-url-website" placeholder="Your website" />
{/* select: single or multi choice */}
<select name="fi-select-plan">
<option value="basic">Basic</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</select>
{/* radio: single choice from a group */}
<label><input type="radio" name="fi-radio-priority" value="low" /> Low</label>
<label><input type="radio" name="fi-radio-priority" value="high" /> High</label>
{/* checkbox: one or more choices, same name = array */}
<label><input type="checkbox" name="fi-checkbox-features" value="api" /> API</label>
<label><input type="checkbox" name="fi-checkbox-features" value="support" /> Support</label>
{/* rating: integer between 1 and 5 */}
<input type="range" name="fi-rating-satisfaction" min={1} max={5} step={1} defaultValue={3} />
{/* date: ISO 8601 date or date-time */}
<input type="date" name="fi-date-appointment" />
{/* file: requires multipart/form-data (FormData uses it by default) */}
<input type="file" name="fi-file-resume" />
{/* country: ISO 3166-1 alpha-2 code. Full list: https://gist.github.com/ssskip/5a94bfcd2835bf1dea52 */}
<select name="fi-country-shipping">
<option value="US">United States</option>
<option value="GB">United Kingdom</option>
</select>
{/* tracking: auto-captured by the SDK from URL params and referrer */}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Send'}
</button>
{status === 'success' && <p>Sent.</p>}
{status === 'error' && <p>Error: {error}</p>}
</form>
);
} # pip install forminit
import os
from forminit import ForminitClient
with ForminitClient(api_key=os.environ["FORMINIT_API_KEY"]) as client:
response = client.submit("YOUR_FORM_ID", {
"blocks": [
# sender: who's submitting (built-in, max 1 per submission)
{
"type": "sender",
"properties": {
"firstName": "Jane",
"lastName": "Doe",
"fullName": "Jane Doe", # alternative to firstName/lastName
"email": "jane@example.com",
"phone": "+12025550123", # E.164 format
"title": "Ms.",
"company": "Acme Corp",
"position": "Software Engineer",
"address": "350 Fifth Avenue",
"address2": "Suite 200",
"city": "New York",
"postcode": "10118",
"country": "US", # ISO 3166-1 alpha-2
"userId": "ext_12345", # your own user identifier
},
},
{
"type": "tracking",
"properties": {
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "summer_sale",
"referrer": "https://google.com/search",
},
},
# text: free-form answer, no validation
{"type": "text", "name": "message", "value": "Hello from Forminit!"},
# number: any numeric value
{"type": "number", "name": "budget", "value": 5000},
# email: a second email beyond the sender's
{"type": "email", "name": "invitee", "value": "colleague@example.com"},
# phone: a second phone beyond the sender's, E.164 format
{"type": "phone", "name": "emergency", "value": "+14155550123"},
# url: validated as a URL
{"type": "url", "name": "website", "value": "https://janedoe.com"},
# select: single value or array of values
{"type": "select", "name": "plan", "value": "Pro"},
# radio: single choice
{"type": "radio", "name": "priority", "value": "high"},
# checkbox: array for multiple selections
{"type": "checkbox", "name": "features", "value": ["api", "support"]},
# rating: integer between 1 and 5
{"type": "rating", "name": "satisfaction", "value": 5},
# date: ISO 8601 date or date-time
{"type": "date", "name": "appointment", "value": "2025-01-15"},
# file: NOT supported in JSON blocks. For uploads use flat form data:
# {"fi-file-resume": ("resume.pdf", open("resume.pdf", "rb"), "application/pdf")}
# country: ISO 3166-1 alpha-2 code. Full list: https://gist.github.com/ssskip/5a94bfcd2835bf1dea52
{"type": "country", "name": "shipping", "value": "GB"},
]
})
print("Submitted! ID:", response["hashId"]) Form blocks
Section titled “Form blocks”Every block you can submit. sender and tracking each appear at most once per submission and hold a fixed set of properties. The rest are blocks you define yourself. Pick a type, give it any name, and Forminit validates the value server-side. The naming pattern is always fi-{blockType}-{name}.
| Form block | Description |
|---|---|
| sender | Who submitted the form. Appears at most once per submission. Properties: email, firstName, lastName, fullName, phone, title, company, position, address, address2, city, postcode, country, userId. At least one of userId, email, phone, firstName, lastName, or fullName is required. FormData fields use fi-sender-{property} (e.g. fi-sender-email). |
| text | Free-form answer for messages, comments, or any string. No validation. |
| number | Any numeric value (budget, quantity). Validated as a valid number. |
| email | A second email beyond the sender’s (invitee, backup contact). Validated as a valid email format. For the submitter’s own email, use sender.email. |
| phone | A second phone beyond the sender’s (emergency contact). E.164 format, e.g. +12025550123. For the submitter’s own phone, use sender.phone. |
| url | A URL (website, LinkedIn). Validated as a valid URL format. |
| select | Dropdown choice. Single value, or an array of values for multi-select. |
| radio | Single choice from a group sharing the same name. |
| checkbox | One or more choices. Multiple checkboxes with the same name produce an array. |
| rating | Integer between 1 and 5. Use for satisfaction scores, reviews, or any 1–5 scale. |
| date | ISO 8601 date or date-time (2025-01-15 or 2025-01-15T10:30:00Z). |
| file | File upload. Requires multipart/form-data; JSON submissions cannot include files. See the File upload guide for size and MIME limits. |
| country | ISO 3166-1 alpha-2 code (e.g. US, GB, DE). Full list. For the submitter’s own country, use sender.country. |
| tracking | Marketing attribution. Appears at most once per submission. Properties: UTM params (utmSource, utmMedium, utmCampaign, utmTerm, utmContent), referrer, and ad click IDs (gclid, fbclid, msclkid, ttclid, twclid, li_fat_id, amzclid, mc_cid, mc_eid, wbraid, gbraid). Auto-captured on the client by the JS SDK; set manually for server-side submissions. |
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Maximum custom blocks per submission | 50 |
| Maximum custom blocks of the same type | 20 |
| Built-in blocks per submission (sender, tracking) | 1 each |
Validation at a glance
Section titled “Validation at a glance”| Block | Rule |
|---|---|
| sender.email, email | Valid email format |
| sender.phone, phone | E.164 format (e.g. +12025550123) |
| sender.country, country | ISO 3166-1 alpha-2 code |
| url | Valid URL format |
| rating | Integer 1–5 |
| date | ISO 8601 date or date-time |
| number | Valid number |
| file | multipart/form-data only |
Editing blocks in the dashboard
Section titled “Editing blocks in the dashboard”After your first submission, your blocks become your form’s schema. From Form Settings → Blocks you can:
- Rename labels so the dashboard reads naturally (e.g. Budget instead of
fi-number-budget) - Reorder how blocks appear in the submission view
- Hide blocks you don’t want to see
Was this page helpful?
Thanks for your feedback.