This article is an overview of how Caldera Forms processes form submissions. It is aimed at developers who are looking to modify the way that forms are processed in order to customize the way a specific form processed submissions or for making a Caldera Forms processor plugin.
In this post, I will be discussing the life-cycle of a submission, as well as the various hooks and functions that are useful to you in modifying this. For a quicker “I need to get the data and do this” please see this article, which is great if you’re doing a quick integration.
There is also a brief, non-complex overview of creating Caldera Forms processor plugins here, as well as a grunt-init task for generating an add-on on Github. I will expand on those soon.
The Caldera Forms API
Caldera Forms submissions are sent via AJAX by default. This can be changed to traditional synchronous processing from the form settings. In both cases the submission is sent via a POST request to a custom endpoint.
Let’s look at the API as an entry point and how returning data works before looking at what happens in between. This way we will have a better understanding of why and how to return errors or success messages.
Please note that Caldera Forms introduced a REST API, but form submission will continue to be processed via the original API until at least Caldera Forms 1.6. This section will be updated when that happens.
Form Submission
The Caldera Forms API works using the WordPress query variable cf_api. That query variable is rewritten as cf-api when pretty permalinks are used. That query variable holds the form ID. API requests therefore are either to /cf-api/<form-id> or ?cf_api=<form-id> depending on your permalink settings.
The API handler sends the data to Caldera_Forms::process_submission()
for processing, which we will discuss shortly.
One important thing to note at the beginning of the process is that if the POST request has a value for ‘control’, Caldera Forms will attempt to restart a previous submission, by passing that value to get_transient
. If there is a save entry, the transient’s value will be set in the global variable $transdata
. If not, $transdata
will be set as an empty array and a new submission will be started.
Responding To Form Submission
When form submission is complete, a redirect will occur if AJAX-mode is not enabled. When AJAX submission are enabled, we hook into the caldera_forms_redirect
to print JSON and exit.
If the global $transdata array mentioned above, has an index “note” that will be the text of the notice displayed above the form after submission. If $transdata has an index “error” and that value is true, then the submission will be considered invalid and the notice will be an error. This important global will be discussed further later in this article.
There is a filter caldera_forms_ajax_return
that can be used to modify or add data to the JSON object being sent back to the browser.
For an example of how to display additional notices after the form is submitted successfully, with both AJAX and non-AJAX submissions, see how I added the PDF download link to the Caldera Form PDF API client plugin in these two functions.
The Form Submission Life Cycle
If you want to follow along at home, open your copy of Caldera Forms, go to classes/caldera-forms.php and look for the Caldera_Forms::process_submission()
method. That’s where most of the magic happens:)
I will know walk you through the important actions that occur while the form is being submitted and why you might want to use those hooks. I will also make note of field validation and the running of processors. This walk through is in order of execution.
The first major hook, which is an action is the caldera_forms_submit_start action. This action fires before anything else really happens besides loading the form. It exposes the form configuration and the process ID. You would use this as the earliest entry point to fire additional events — analytics or conversion tracking — or short-circuit the rest of the processing, if you wanted to work with the raw POST data yourself.
The process ID that is exposed by this hook is very important to understand. Because form submissions may happen over multiple requests, a form submission is assigned a unique ID for tracking partial submission data in a transient.
At this point the process ID is likely not set, but if the form is being re-submitted following a validation error of some sort, the process ID will be set.
Field Values and Validation
At this point fields are loaded. There are three filters you can use to modify fields. They are documented here. One important thing to note about form configurations is that if they have certain arguments those modify how the field data is processed or saved and this is the last opportunity to work with those arguments.
These three arguments are:
- handler – A name of a function to call when processing the submission for this form for this field type.
- save – A name of a function to call when saving the data from this field type.
- validate – A name of a function to call when validating the value for fields of this type.
All three of these handlers are used to create a dynamically names hook caldera_forms_process_field_{field_type}
. Many field types use this type of filter for handling field saving that is not standard. For example, file fields.
At this point, if this is a new submission, the data for a new transient will be set in the global $transdata array. This value can be modified using the caldera_forms_submit_transient_setup
filter.
At this point, if the POST data has the field ‘_cf_frm_edt’, then this will be considered to be a request to edit a previously saved entry using the value of $_POST[ '_cf_frm_edt' ]
as the entry ID, if the edit token is valid.
Now field data will be gotten from each field. This process happens by looping through each field and setting its value using Caldera_Forms::get_field_data()
, which processes the data from the incoming POST request. Once Caldera_Forms::get_field_data()
has run once, Caldera_Forms::get_field_data()
, if called again for the same field, will pull from that processed data, not the raw POST data.
Then there is an opportunity to validate fields using a caldera_forms_validate_field_{$field-type}
. If that filter or Caldera_Forms::get_field_data()
returns an object of the WP_Error
class, that will break form submission and send a failed validation message.
Field validation is covered more fully in my tutorial on creating a custom field validation processor.
This loop through each field will also run through conditional logic and required field checks. This is intentionally redundant to client-side validation, but is necessary as client-side validation can be defeated in the browser.
Form Processors
Now that field values are set and known to be valid, form processor will be run. Form processing happens in three steps:
- Pre-process: Used for any process that is required to be successful for the submission to be complete. This is the most commonly stage.
- Process: Runs after pending entry is created. Not commonly used, but provides an opportunity to return errors, after all pre-processing is complete.
- Post-process: Runs before final entry saving is performed and emails are sent. Useful for modifying field values, or adding extra meta data to entry.
For each step, an action is fired, all processor callbacks added by processors added to this from are looped through and then another action is fired.
The pre-process stage runs first. At this stage, if a processors returns data, that will break submission and send an validation error back. For example, a payment processor would attempt to make a payment via a payment gateway API and if it failed it would return an error to the browser here.
A pre-processor returning an error looks like this:
return array( 'error' => true, 'note' => 'You are not a dog! You are a cat!' );
Before pre-processing loop, the caldera_forms_submit_pre_process_start
action is fired. After the pre-prcoessing loop, the caldera_forms_submit_pre_process_end
action is fired.
If pre-processing does not generate errors, the entry is actually saved in the database with the status of “pending”. That status will be changed once the processing is completed, but it is assumed at this point that this entry is most likely valid and will be completed. The process loop may change that.
After saving the entry the caldera_forms_entry_saved
action is fired. This is the earliest action you can use to capture the entry ID for user elsewhere in your code.
At this point the caldera_forms_submit_process_start
action is fired and then the process loop starts. Any processor with a “process” callback will execute that callback here. This is the last opportunity to generate a validation error. For the most part validation should be done at pre-process, but its impossible to know if all processors have not created an error at pre-process, until the process loop.
Reporting errors during the process loop is different. Any data returned by the process callback of a processor will be saved as entry meta. But after each process callback is run, the global $transdata
array will be checked for errors and if $transdata[ 'error' ]
is true, and error will be sent back to the browser and processing will be aborted.
After the process loop, the caldera_forms_submit_process_end
action is fired.
At this point the post-process loop begins. It is proceeded by the caldera_forms_submit_post_process/
action and followed by the caldera_forms_submit_post_process_end/
action.
Any data returned by the post-process callbacks of form processors will be saved as entry meta.
Finishing Up
At this point the submission is ready to be finalized. The entry data is already in the database, but it needs some finalizing.
This process starts by firing the caldera_forms_submit_complete action. This action’s name is a little misleading. The submission processing is done in terms of validation and running processors, but the final entry saving has not happened, nor has an email been sent.
If you’re reading the code, that looks like the end of the process besides the final redirect or AJAX response, but there is a function, Caldera_Forms::save_final_form
, hooked to caldera_forms_submit_complete
by default.
If you want to modify the submission before the entry is saved or the email notification is sent, hook into caldera_forms_submit_complete
with a priority less than 50.
Caldera_Forms::save_final_form
finalizes the entry. If database saving is in use it will call Caldera_Forms_Save_Final::save_in_db()
to finalize saving the entry. If the mailer is enabled, Caldera_Forms_Save_Final::do_mailer()
will be called to process the mailer.
Where Does This Code Go?
When using WordPress hooks to customize Caldera Forms or other plugins you should not modify the plugin files, or you will lose your changes when you update the plugin. Instead you should create a small plugin to hold the custom code. It's easy, learn how here.
Technically you can add the custom code to your theme's functions.php, but then you will not be able to change your theme and keep these customizations.