Introduction
The performance of form submissions can be an important factor in your site’s user experience, so it is essential that you understand how the process works. Factors that can impact performance include:
- The user’s browser, device, and internet connection when submitting the form.
- The network between the user’s device and the server hosting your site.
- The available resources on the server hosting your site.
- The site database (MySQL or MariaDB) version and configuration.
- PHP version and configuration.
- WordPress version.
- The active theme (and child theme).
- The active plugins.
- The number of users visiting the site around the time of the submission.
- The number of users submitting forms is around the same time.
- Form complexity: the number of fields, pages, conditional logic, and calculations.
- Integrations: the performance of other services the form integrates with, such as email marketing, CRMs, and payment gateways.
The rest of this article focuses on troubleshooting the Gravity Forms part of the process.
Note: Gravity Forms is just one part of your site. WordPress, the theme, and other plugins will also be running their own processes before, during, and after the form submission processes described below.
Getting Started
Begin troubleshooting by:
- Enabling logging on the Forms > Settings page. Don’t forget to save the settings once logging is enabled.
- On the Forms > Settings > Logging page, ensure that logging is enabled for Gravity Forms Core and all add-ons.
- Go to the page where your form is embedded, fill out and submit the form so the logging statements and their timings are recorded.
- View the logs via the Forms > Settings > Logging or Forms > System Status pages.
Check our logging and debugging documentation for additional help.
Reviewing the Core Log
Note: The example logging statements found below are just a few of the key events to look out for. Some of them are specific to Gravity Forms 2.7 and greater. The logs can contain many more logging statements than are documented here.
To narrow down where bottlenecks are in the form submission process, you would compare the date and time portions at the start of each logging statement. This uses the format: Year-Month-Day Hour:Minutes:Seconds.Microseconds e.g. 2023-01-16 16:26:31.894715.
Processing Starts
When viewing the core log, the first logging statement recorded for a submission is from GFFormDisplay::process_form(), it identifies the ID of the form being processed.
[date and time] - DEBUG --> GFFormDisplay::process_form(): Starting to process form (#[Form ID]) submission.
This is followed by a logging statement that identifies the source and target page numbers.
[date and time] - DEBUG --> GFFormDisplay::process_form(): Source page number: 1. Target page number: 0.
A target page number of 0 indicates the form was submitted with the intention of saving the entry, if the submission is valid.
Validation
Note: Performance during validation can be impacted by the number of fields on the form, calculations, conditional logic, and add-ons that integrate using the field or form validation filters, such as payment add-ons communicating with payment gateways.
Once the form, source, and target page numbers are identified, validation can begin. It starts by validating any restrictions configured in the form settings, such as the schedule and entry limits.
[date and time] - DEBUG --> GFFormDisplay::validate(): Starting for form #[Form ID].
[date and time] - DEBUG --> GFFormDisplay::validate(): Checking restrictions.
[date and time] - DEBUG --> GFFormDisplay::validate(): Completed restrictions. Starting field validation.
Once those checks are complete, the fields are validated.
[date and time] - DEBUG --> Field validation completed in [number] seconds.
After field validation is completed, integrations, such as payment add-ons, can run checks using the gform_validation filter.
[date and time] - DEBUG --> Executing functions hooked to gform_validation.
[date and time] - DEBUG --> Completed gform_validation.
Finally, the validation result is returned, so submission can continue.
[date and time] - DEBUG --> GFFormDisplay::process_form(): After validation. Is submission valid? Yes.
[date and time] - DEBUG --> GFFormDisplay::process_form(): Submission is valid. Moving forward.
Saving the Entry
When the form is valid, the next step is saving the entry.
Note: Performance during entry save can be impacted by the number of fields on the form, calculations, conditional logic, and the server(s) hosting the site and WordPress database.
The first stage of saving the entry is adding the entry properties, such as the current date and time, to the gf_entry table.
[date and time] - DEBUG --> GFFormsModel::save_entry(): Saving entry.
[date and time] - DEBUG --> GFFormsModel::save_entry(): Entry record created in the database. ID: [Entry ID].
[date and time] - DEBUG --> GFFormsModel::save_entry(): Saving entry fields.
Once the WordPress database has returned the ID it assigned the new entry, the field values can be queued for batch saving to the gf_entry_meta table.
The label, ID, and type of each field being saved will be recorded in the log.
[date and time] - DEBUG --> GFFormsModel::queue_save_input_value(): Queued field operation: [Field Label](#[Field ID] - [Field or Input Type]).
Once the values have been batch saved to the database, you’ll find the following logging statements.
[date and time] - DEBUG --> GFFormsModel::save_entry(): Finished saving entry fields.
[date and time] - DEBUG --> GFFormsModel::save_entry(): Saving entry completed in [number] seconds
Next up is the saving of entry meta for add-ons.
[date and time] - DEBUG --> GFFormsModel::set_entry_meta(): Saving meta for entry (#[Entry ID]) completed in [number] seconds.
Triggering Integrations
Next up are the configured integrations, starting with anti-spam checks.
Note: Performance at this stage of the submission can be impacted by the number of integrations that are configured for the form, the number of feeds using conditional logic, and the performance of the services add-ons integrate with.
[date and time] - DEBUG --> GFCommon::is_spam_entry(): Executing functions hooked to gform_entry_is_spam.
[date and time] - DEBUG --> GFCommon::is_spam_entry(): Result from gform_entry_is_spam filter: false
[date and time] - DEBUG --> GFCommon::is_spam_entry(): Spam checks completed in [number] seconds. Is submission considered spam? No.
Integrations such as add-on feeds are then processed. Most add-ons won’t process an entry that has been identified as spam.
[date and time] - DEBUG --> GFFormDisplay::handle_submission(): Executing functions hooked to gform_entry_created.
[date and time] - DEBUG --> GFFormDisplay::handle_submission(): Completed gform_entry_created.
[date and time] - DEBUG --> GFFormDisplay::handle_submission(): Executing functions hooked to gform_entry_post_save.
[date and time] - DEBUG --> GFFormDisplay::handle_submission(): Completed gform_entry_post_save.
[date and time] - DEBUG --> GF_Background_Process::dispatch(): Running for gf_feed_processor.
Each add-on has a log file recording how it processed the submission, so you’ll also want to review the timings of the logging statements in the add-on logs.
Legacy Post Creation
Next up is the built-in legacy post creation functionality.
Performance at this stage of the submission can be affected by the number of form fields and the number of file uploads to be added to the media library.
Forms without legacy Post fields
Even if your form isn’t using the legacy post fields, you’ll find the following in the log:
[date and time] - DEBUG --> GFFormsModel::create_post(): Starting.
[date and time] - DEBUG --> GFFormsModel::create_post(): Stopping. The form doesn't have any post fields.
Forms with legacy Post fields
Create the Post
The first stage of post creation is to retrieve data from all Post fields and create the initial post object.
[date and time] - DEBUG --> GFFormsModel::create_post(): Starting.
[date and time] - DEBUG --> GFFormsModel::create_post(): Getting post fields.
[date and time] - DEBUG --> GFFormsModel::create_post(): Inserting post via wp_insert_post().
[date and time] - DEBUG --> GFFormsModel::create_post(): Result from wp_insert_post(): [Post ID]
Process Media
If the form has any Post Image fields, their files are copied to the media library and attached to the post.
[date and time] - DEBUG --> GFFormsModel::create_post(): Processing post images.
[date and time] - DEBUG --> GFFormsModel::create_post(): No image to process for field #[Field ID]
[date and time] - DEBUG --> GFFormsModel::create_post(): Field #[Field ID]. URL: [File URL]
[date and time] - DEBUG --> GFFormsModel::media_handle_upload(): Image copied to the media directory. Result from wp_insert_attachment(): [Attachment ID]
If the Set as Featured Image setting is enabled on the field, you’ll also find the following:
[date and time] - DEBUG --> GFFormsModel::create_post(): Setting the featured image. Result from set_post_thumbnail(): [bool]
Add Custom Fields
Next, if the form has any Post Custom Fields, their values will be added to the post meta.
[date and time] - DEBUG --> GFFormsModel::create_post(): Adding custom fields.
[date and time] - DEBUG --> GFFormsModel::create_post(): Getting custom field: [Meta Key]
Process Templates
If the Create Content Template setting is used on any Post field, you’ll see logging showing the content templates being processed and the post being updated.
[date and time] - DEBUG --> GFFormsModel::process_post_template(): Processing post_content template.
[date and time] - DEBUG --> GFFormsModel::create_post(): Updating post.
Update Entry
The final step of the legacy post creation feature is updating the entry with the ID of the created post.
[date and time] - DEBUG --> GFFormsModel::create_post(): Updating entry with post id.
Triggering Notifications
The next stage in the form submission process is triggering notifications assigned to the default ‘Form is submitted’ event.
Background Notifications Disabled
This is where most delays occur.
Note: Performance at this stage of the submission can be impacted by the number of notifications, their use of routing and conditional logic, and the attaching of uploaded files. It can also be affected by WordPress configuration, the server hosting the site, and SMTP/email service plugins.
2023-01-16 16:26:31.894715 - DEBUG --> GFAPI::send_notifications(): Gathering notifications for form_submission event for entry #[Entry ID].
2023-01-16 16:26:31.894812 - DEBUG --> GFCommon::send_notifications(): Processing notifications for form_submission event for entry #[Entry ID]: Array
(
[0] => [Notification ID]
)
(only active/applicable notifications are sent)
2023-01-16 16:26:31.894885 - DEBUG --> GFCommon::send_notification(): Starting to process notification (#[Notification ID] - [Notification Name]).
2023-01-16 16:26:31.895328 - DEBUG --> GFCommon::send_email(): Sending email via wp_mail().
2023-01-16 16:26:31.895404 - DEBUG --> [email details removed from log]
2023-01-16 16:26:46.920707 - DEBUG --> GFCommon::send_email(): Result from wp_mail(): 1
2023-01-16 16:26:46.920818 - DEBUG --> GFCommon::send_email(): WordPress successfully passed the notification email (#[Notification ID] - [Notification Name]) for entry #[Entry ID] to the sending server.
2023-01-16 16:26:46.923607 - DEBUG --> GFCommon::send_notifications(): Sending notifications for entry (#[Entry ID]) completed in 15.021593 seconds.
In this example, you can see that it took WordPress and the server hosting the site 15 to 16 seconds to send the email and pass back the result. That’s a substantial delay before the user sees the form confirmation, which would be even longer if the form had multiple notifications assigned to be sent on form submission.
Background Notifications Enabled
[date and time] - DEBUG --> GFAPI::send_notifications(): Gathering notifications for form_submission event for entry #[Entry ID].
[date and time] - DEBUG --> GFAPI::send_notifications(): Adding [count] notification(s) to the async processing queue for entry #[Entry ID].
[date and time] - DEBUG --> Gravity_Forms\Gravity_Forms\Async\GF_Background_Process::save(): Saving batch [batch_id]. Tasks: [task_count].
[date and time] - DEBUG --> Gravity_Forms\Gravity_Forms\Async\GF_Background_Process::dispatch_on_shutdown(): Dispatch delayed until shutdown for gf_notifications_processor.
Preparing the Confirmation
After triggering notifications, the form confirmation is prepared for output.
Note: Performance in this final stage of submission processing can be impacted by the number of confirmations and their use of conditional logic.
For forms with multiple confirmations, it will loop through them, testing the conditional logic to determine which should be used for the current entry. If none of the conditional logic rules matched, it will fall back to the default confirmation.
[date and time] - DEBUG --> GFFormDisplay::handle_confirmation(): Preparing confirmation (#[Confirmation ID] - [Confirmation Name]).
If the theme or another plugin is using the gform_confirmation filter, such as payment add-ons that redirect to hosted payment pages, you’ll also find the following logging statements.
[date and time] - DEBUG --> GFFormDisplay::handle_confirmation(): Executing functions hooked to gform_confirmation.
[date and time] - DEBUG --> GFFormDisplay::handle_confirmation(): Completed gform_confirmation.
We then log the confirmation message or the redirect that will be used.
[date and time] - DEBUG --> GFFormDisplay::handle_confirmation(): Confirmation to be used => <div id='gform_confirmation_wrapper_740' class='gform_confirmation_wrapper '><div id='gform_confirmation_message_740' class='gform_confirmation_message_740 gform_confirmation_message'>Thanks for contacting us! We will get in touch with you shortly.</div></div>
Processing Completes
If the theme or another plugin is using the gform_after_submission hook, you’ll also find the following:
[date and time] - DEBUG --> GFFormDisplay::process_form(): Executing functions hooked to gform_after_submission.
[date and time] - DEBUG --> GFFormDisplay::process_form(): Completed gform_after_submission.
And here’s the last logging statement to be output by GFFormDisplay::process_form(), it contains the total run time for that method.
[date and time] - DEBUG --> GFFormDisplay::process_form(): Processing completed in [number] seconds.
All of the above occurs before WordPress, the theme, and other active plugins determine which template to use to render the requested page.
As WordPress is preparing to output the scripts for the page, you might find the following, which indicates the scripts to prevent duplicate submissions are being queued for output:
[date and time] - DEBUG --> Gravity_Forms\Gravity_Forms\Duplicate_Submissions\GF_Duplicate_Submissions_Handler::is_valid_submission(): form #[Form ID]. entry #[Entry ID].
Finally, as WordPress is processing shortcodes and blocks in the content of the page to be displayed, you might find the following:
[date and time] - DEBUG --> GFFormDisplay::get_form(): Preparing form (#[Form ID]) confirmation completed in [number] seconds.
Improving Performance
Refer to the How to Improve Form Submissions Performance article for more information
Disclaimer: Third-party services, plugins, or code snippets that are referenced by our Support documentation or in Support Team communications are provided as suggestions only. We do not evaluate, test or officially support third-party solutions. You are wholly responsible for determining if any suggestion given is sufficient to meet the functional, security, legal, ongoing cost and support needs of your project.
Feedback, feature, and integration requests, and other functionality ideas can be submitted at http://forms.roadmap.gravity.com/.