AJAX Form Submission
Last updated Sep 3rd, 2026 | Page history | Improve this page | Report an issue
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$194 per month—let's make that $500!
Learn moreAJAX Form Submission¶
From FormIt 5.2.0, forms can submit via AJAX without a full page reload. Validation errors, success messages, and redirects are handled by the built-in formit.js. The same script drives reCAPTCHA v3 token requests before POST.
Setup¶
Set the formit.frontend_js system setting (a fresh FormIt install already has this default):
| Setting | Value |
|---|---|
formit.frontend_js |
js/web/formit.js |
FormIt registers assets/components/formit/js/web/formit.js and passes the action.php URL into the JS config. If the recaptcha hook is active and a site key is set, it also registers Google api.js?render=....
How It Works¶
- Before a POST, the FormIt snippet stores its configuration (hooks, validation rules, and so on) in the session and cache, and outputs a random token as
[[!+fi.ajaxToken]](32 hex characters fromrandom_bytes, not an MD5 hash). - You put that token on the
<form>asdata-formit-ajax-token. - On page load,
formit.jsattaches to every<form>. AJAX runs only whendata-formit-ajax-tokenis present. Without that attribute, if the form hasg-recaptcha-response, the script still intercepts submit, fills the reCAPTCHA token, then does a normal POST. - In AJAX mode the JS posts form data with the
X-FormIt-Tokenheader viafetch()toaction.php. - The endpoint loads the stored configuration by token, runs FormIt server-side, and returns JSON.
- The JS updates the DOM: field errors, validation messages, success, or redirect.
Snippet Call¶
The snippet call is the same as for a regular form. No special parameters are needed for AJAX:
[[!FormIt?
&hooks=`email,redirect`
&emailTpl=`MyEmailChunk`
&emailTo=`[email protected]`
&emailFrom=`[[++emailsender]]`
&redirectTo=`123`
&validate=`name:required,
email:email:required,
subject:required,
text:required:stripTags`
]]
Form HTML¶
To make a form work with AJAX, add the data-formit-ajax-token attribute to the <form> tag and use data-formit-* attributes for error and message elements:
<form action="[[~[[*id]]]]" method="post" class="form"
data-formit-ajax-token="[[!+fi.ajaxToken]]">
<div data-formit-validation-error-message>[[!+fi.validation_error_message]]</div>
<div data-formit-success-message>[[!+fi.successMessage]]</div>
<div class="form-field">
<label for="name">Name:</label>
<input type="text" name="name" id="name" value="[[!+fi.name]]" />
<span data-formit-error="name">[[!+fi.error.name]]</span>
</div>
<div class="form-field">
<label for="email">Email:</label>
<input type="text" name="email" id="email" value="[[!+fi.email]]" />
<span data-formit-error="email">[[!+fi.error.email]]</span>
</div>
<div class="form-field">
<label for="subject">Subject:</label>
<input type="text" name="subject" id="subject" value="[[!+fi.subject]]" />
<span data-formit-error="subject">[[!+fi.error.subject]]</span>
</div>
<div class="form-field">
<label for="text">Message:</label>
<textarea name="text" id="text" cols="55" rows="7">[[!+fi.text]]</textarea>
<span data-formit-error="text">[[!+fi.error.text]]</span>
</div>
<div class="form-field">
<label for="numbers">Numbers:</label>
<select name="numbers" id="numbers">
<option value="">Select an option...</option>
<option value="one" [[!+fi.numbers:FormItIsSelected=`one`]]>One</option>
<option value="two" [[!+fi.numbers:FormItIsSelected=`two`]]>Two</option>
<option value="three" [[!+fi.numbers:FormItIsSelected=`three`]]>Three</option>
</select>
<span data-formit-error="numbers">[[!+fi.error.numbers]]</span>
</div>
<div class="form-field">
<label>Colors:</label>
<input type="hidden" name="colors[]" value="" />
<ul>
<li><label><input type="checkbox" name="colors[]" value="red" [[!+fi.colors:FormItIsChecked=`red`]] /> Red</label></li>
<li><label><input type="checkbox" name="colors[]" value="blue" [[!+fi.colors:FormItIsChecked=`blue`]] /> Blue</label></li>
<li><label><input type="checkbox" name="colors[]" value="green" [[!+fi.colors:FormItIsChecked=`green`]] /> Green</label></li>
</ul>
<span data-formit-error="colors">[[!+fi.error.colors]]</span>
</div>
<div class="form-buttons">
<input type="submit" value="Send Contact Inquiry" />
</div>
</form>
Note: The
actionattribute is kept as a fallback for when JavaScript is disabled. In AJAX mode the form is submitted toaction.phpinstead.
Data Attributes Reference¶
| Attribute | Element | Description |
|---|---|---|
data-formit-ajax-token |
<form> |
Activates AJAX mode. Value must be [[!+fi.ajaxToken]]. |
data-formit-error="fieldname" |
<span> |
Displays the validation error for a specific field. The JS fills innerHTML with the error text. |
data-formit-validation-error-message |
<div> |
Displays the general validation error message (equivalent of [[!+fi.validation_error_message]]). |
data-formit-success-message |
<div> |
Displays the success message (from &successMessage property). |
data-formit-error-message |
<div> |
Displays hooks error message (from [[!+fi.error_message]]). |
All data-formit-error and message elements are cleared before each submission.
JavaScript API¶
Auto-initialization¶
On DOMContentLoaded, formit.js initializes every <form> on the page. You do not need extra JS for the basic flow. AJAX for a given form is enabled only by data-formit-ajax-token.
JavaScript Events¶
The form element dispatches CustomEvents that you can listen to with addEventListener. All events bubble.
| Event | Cancelable | event.detail |
Description |
|---|---|---|---|
formit:beforesubmit |
Yes | { form } |
Fired before the reCAPTCHA token request / submit. Call event.preventDefault() to cancel. |
formit:success |
No | { data } |
Fired on successful submission. |
formit:error |
No | { data } |
Fired when validation fails. |
formit:complete |
No | {} |
Fired after every request (success or error). |
formit:redirect |
Yes | { url } |
Fired before redirect. Call event.preventDefault() to cancel. |
Example:
document.getElementById('my-form').addEventListener('formit:success', function (e) {
alert('Thank you! Your form has been submitted.');
});
Redirect Handling¶
When the redirect hook is used, AJAX mode does not perform a server-side redirect. Instead:
- The server returns a
redirect_urlfield in the JSON response. - The JS dispatches a cancelable
formit:redirectevent. - If not canceled (via
event.preventDefault()oronRedirectreturningfalse), the JS setswindow.location.hrefto the redirect URL.
This lets you intercept the redirect and handle it your own way (e.g., load content via AJAX, show a thank-you message in place, etc.).
CSS Loading State¶
While the reCAPTCHA token request and/or AJAX request runs:
- The
formit-loadingCSS class is added to the<form>element. - All
[type="submit"]buttons inside the form are set todisabled.
Both are removed when the flow finishes (or if reCAPTCHA fails).
You can use this to style a loading indicator:
.formit-loading {
opacity: 0.6;
pointer-events: none;
}
JSON Response Structure¶
For advanced use cases, the AJAX endpoint returns a JSON object with this structure:
{
"success": true,
"message": "Success message or error message",
"redirect_url": "https://example.com/thank-you",
"placeholders": {
"error.name": "<span class=\"error\">This field is required.</span>",
"error.email": "<span class=\"error\">Please enter a valid email.</span>",
"validation_error_message": "<p class=\"error\">A form validation error occurred.</p>",
"successMessage": "Form submitted successfully."
}
}
| Field | Type | Description |
|---|---|---|
success |
Boolean | true if the form passed all validation and hooks succeeded. |
message |
String | Success or error message. |
redirect_url |
String | Present only when the redirect hook is active. |
placeholders |
Object | All FormIt placeholders (with the fi. prefix stripped). Field errors are under error.fieldname. |
See Also¶
- FormIt.Hooks
- FormIt.Validators
- FormIt.FormItRetriever
- FormIt.Tutorials and Examples
- FormIt.FormItCountryOptions
- FormIt.FormItStateOptions
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$194 per month—let's make that $500!
Learn more










