TL;DR
- Gravity Forms exposes its lifecycle through actions (run code when something happens) and filters (change a value and return it).
- A submission runs, in order: validation hooks →
gform_pre_submission→ entry saved →gform_entry_created→ notifications →gform_confirmation→gform_after_submission, which fires last and also runs for spam entries. - Most hooks take a form ID suffix —
gform_after_submission_5runs only for form 5 — and field hooks also take a field ID:gform_field_validation_5_3. - Entry edits fire two different hooks:
gform_after_update_entryfor the wp-admin entry screen,gform_post_update_entryforGFAPI::update_entry().
/ Overview
What are Gravity Forms hooks?
Gravity Forms hooks are the WordPress actions and filters the plugin fires as a form moves through its lifecycle — render, validation, submission, entry storage, notifications, confirmation, and later edits to the entry. You attach a callback with add_action() or add_filter() and run your own logic at exactly that moment, without editing the plugin.
There are hundreds of them. This reference covers the twenty or so that come up in real projects, grouped by where they fire, with each hook's type, arguments and one working example. Every signature below was checked against the official Gravity Forms documentation¹, and the firing order against the plugin source (GFFormDisplay::process_form(), Gravity Forms 2.7).
/ Actions vs filters
How do Gravity Forms actions and filters differ?
Actions let you do something; filters let you change something. An action callback receives data and returns nothing — gform_after_submission hands you the saved entry. A filter callback receives a value and must return it, modified or not — gform_pre_render hands you the form and expects the form back. Forget the return in a filter and you blank out the value Gravity Forms was about to use.
Two more conventions apply to almost every hook below. Append a form ID to scope it to one form (gform_pre_render_12); field-level hooks also accept _FORMID_FIELDID. And when a hook passes more than one argument, the fourth parameter of add_action() / add_filter() must say how many you want — the default is 1.
/ At a glance
Which Gravity Forms hooks fire, and when?
The table lists each hook in the order a submission meets it. The sections after it go group by group.
| Hook | Type | Fires | Arguments |
|---|---|---|---|
gform_pre_render | Filter | Before the form is displayed | $form, $ajax, $field_values, $context |
gform_field_value_{name} | Filter | Per field, while populating defaults | $value, $field, $name |
gform_submit_button | Filter | While rendering the form footer | $button_input, $form |
gform_post_render (JS) | jQuery event | Every render, incl. multi-page steps | event, form_id, current_page |
gform_pre_validation | Filter | Before validation runs | $form |
gform_field_validation | Filter | On submit, once per field | $result, $value, $form, $field, $context |
gform_validation | Filter | On submit, for the whole form | $validation_result, $context |
gform_validation_message | Filter | When validation fails | $message, $form |
gform_pre_submission | Action | After validation, before the entry is saved | $form |
gform_pre_submission_filter | Filter | Right after gform_pre_submission | $form |
gform_save_field_value | Filter | Per input, as the value is saved | $value, $entry, $field, $form, $input_id |
gform_entry_created | Action | Entry saved, before notifications | $entry, $form |
gform_entry_post_save | Filter | Right after gform_entry_created | $entry, $form |
gform_notification | Filter | Per notification, before it becomes an email | $notification, $form, $entry |
gform_pre_send_email | Filter | Just before wp_mail() | $email, $message_format, $notification, $entry |
gform_after_email | Action | After wp_mail() returns | $is_success, $to, $subject, … $entry |
gform_confirmation | Filter | When the confirmation is built | $confirmation, $form, $entry, $is_ajax |
gform_after_submission | Action | Last step of the submission | $entry, $form |
gform_after_update_entry | Action | Entry edited on the wp-admin entry screen | $form, $entry_id, $original_entry |
gform_post_update_entry | Action | Entry edited via GFAPI::update_entry() | $entry, $original_entry |
gform_post_payment_completed | Action | A payment add-on marks a payment complete | $entry, $action |
/ Render
Which hooks change a form before it is displayed?
gform_pre_render (filter) receives the form object before it is displayed and must return it. It is the standard way to inject dropdown choices, hide fields or change labels. Its full signature is $form, $ajax, $field_values, $context, where $context is form_display or form_config. The official docs recommend hooking the same callback to gform_pre_validation, gform_pre_submission_filter and gform_admin_pre_render too, so the injected choices also exist during validation, in merge tags and in the admin entry view². On a page served from a full-page cache, none of this PHP runs.
PHP — populate a dropdown from code (form 5, field 4)
add_filter( 'gform_pre_render_5', 'fs_populate_plans' ); add_filter( 'gform_pre_validation_5', 'fs_populate_plans' ); add_filter( 'gform_pre_submission_filter_5', 'fs_populate_plans' ); add_filter( 'gform_admin_pre_render_5', 'fs_populate_plans' ); function fs_populate_plans( $form ) { foreach ( $form['fields'] as $field ) { if ( $field->id != 4 ) { continue; } $field->choices = [ [ 'text' => 'Starter', 'value' => 'starter' ], [ 'text' => 'Pro', 'value' => 'pro' ], ]; } return $form; // a filter must return what it was given }
gform_field_value_{parameter_name} (filter) sets a default for one field. It only runs for fields with Allow field to be populated dynamically checked and a parameter name set in the field's Advanced tab; gform_field_value_email fills the field whose parameter name is email. For choice fields, the returned value must match the choice label exactly, or the value when Show Values is on. The unsuffixed gform_field_value runs for every dynamic field and receives $value, $field, $name.
gform_submit_button (filter) receives the submit button's HTML and the form, and returns replacement markup — the usual way to swap the <input> for a <button> with an icon.
gform_post_render is a JavaScript event, not a PHP hook. It fires on every render: first load, each page of a multi-page form, a re-render with validation errors, and the AJAX confirmation. In Gravity Forms 2.9.0 it was replaced by the native gform/post_render event³.
JS — run code after each render
// Gravity Forms 2.9 and later document.addEventListener( 'gform/post_render', ( event ) => { const formId = event.detail.formId; const currentPage = event.detail.currentPage; } ); // Before 2.9 jQuery( document ).on( 'gform_post_render', function ( event, formId, currentPage ) { // same moment, jQuery event } );
/ Validation
Which hooks validate a submission?
Validation runs in three steps. gform_pre_validation (filter, $form) lets you adjust the form object first — the place to re-add choices injected by gform_pre_render. Then gform_field_validation (filter) runs once per field and receives $result, $value, $form, $field, plus $context since Gravity Forms 2.6.3.2 (form-submit, api-submit or api-validate). Return $result with is_valid and message set. It takes the most specific suffix of any hook here: gform_field_validation_6_1 runs only for field 1 of form 6.
PHP — reject one field (form 5, field 3)
add_filter( 'gform_field_validation_5_3', function ( $result, $value, $form, $field ) { if ( $result['is_valid'] && intval( $value ) <= 10 ) { $result['is_valid'] = false; $result['message'] = 'Please enter a number greater than 10.'; } return $result; }, 10, 4 );
gform_validation (filter) sees the whole form at once: $validation_result holds is_valid and the form object, so you can mark several fields failed after a cross-field check or a remote lookup. The official docs carry one warning worth repeating: do not use the form-specific gform_validation_FORMID on forms that also have payment add-on feeds — use the generic hook and check the form ID inside it⁴. Finally, gform_validation_message (filter, $message, $form) replaces the banner shown above a form that failed.
A failed validation means no entry, no notifications and no gform_after_submission. That makes validation the place to stop bad data, rather than a later hook.
/ Submission
Which hooks fire when a form is submitted and the entry is saved?
gform_pre_submission (action, $form) runs after validation passes and before anything is saved — the last point to change posted values, which still live in $_POST ($_POST['input_5'] for field 5). gform_pre_submission_filter (filter) runs immediately after it and returns a modified form object, which is what notifications will see.
While the entry is written, gform_save_field_value (filter) runs for each input with $value, $entry, $field, $form, $input_id. It is a low-level hook: pair it with gform_get_input_value to encrypt a value on save and decrypt it on read.
Once the entry row exists, gform_entry_created (action, $entry, $form) fires, then gform_entry_post_save (filter, returns $entry). Both run before notifications are sent and the confirmation is processed, and both run for entries flagged as spam.
gform_after_submission (action, $entry, $form) is the last step, after notifications and the confirmation. The official docs note it also runs for entries marked as spam⁵, so an integration should check $entry['status']. Read values with rgar(), which returns an empty string instead of a PHP notice for a missing key. Single fields are keyed by field ID; multi-input fields like Name and Address use the input ID, such as 3.3 for first name.
PHP — read a saved entry (form 5)
add_action( 'gform_after_submission_5', function ( $entry, $form ) { if ( rgar( $entry, 'status' ) === 'spam' ) { return; // this hook runs for spam entries too } $entry_id = rgar( $entry, 'id' ); $email = rgar( $entry, '2' ); // field 2 $first = rgar( $entry, '3.3' ); // Name field 3, first-name input $page_url = rgar( $entry, 'source_url' ); }, 10, 2 );
/ Notifications
Which hooks change notifications and confirmations?
Notifications go through three hooks, and none of them run for spam entries. gform_notification (filter, $notification, $form, $entry) changes the notification object — recipients, subject, message, routing — before it is turned into an email. gform_pre_send_email (filter, $email, $message_format, $notification, $entry) gets the finished email just before wp_mail(). Setting $email['abort_email'] = true cancels that one send. gform_after_email (action) fires afterwards with $is_success, the email parts and the entry — the place to log a failed send.
PHP — skip one notification for test entries
add_filter( 'gform_pre_send_email', function ( $email, $message_format, $notification, $entry ) { if ( rgar( $notification, 'name' ) === 'Admin Notification' && rgar( $entry, '5' ) === 'test' ) { $email['abort_email'] = true; } return $email; }, 10, 4 );
gform_confirmation (filter, $confirmation, $form, $entry, $is_ajax) returns either a message string or an array with a redirect key. gform_confirmation_anchor (filter) decides whether the page scrolls to the form after submitting: true, false, or a pixel offset from the top.
PHP — conditional redirect (form 5)
add_filter( 'gform_confirmation_5', function ( $confirmation, $form, $entry, $is_ajax ) { if ( rgar( $entry, '4' ) === 'pro' ) { $confirmation = [ 'redirect' => 'https://example.com/pro-onboarding/' ]; } return $confirmation; }, 10, 4 );
/ Entry edits
Which hooks fire when an existing entry is updated?
Two different hooks, depending on how the entry is edited — and neither fires for the other path.
gform_after_update_entry (action) fires only after an entry is saved on the wp-admin entry detail screen. It passes $form, $entry_id and $original_entry (the pre-edit entry, available since Gravity Forms 1.9.12.9)⁶. It passes an ID, not the updated entry, so load it with GFAPI::get_entry(). Register it with 3 accepted arguments. The usage line in the official docs shows 2, which silently drops $original_entry.
gform_post_update_entry (action) fires after GFAPI::update_entry() — that is, edits made by code, add-ons and imports. It passes the updated $entry and $original_entry. If you need to react to every edit, hook both.
PHP — react to an edit, from either path
// Edited on the wp-admin entry detail screen add_action( 'gform_after_update_entry', function ( $form, $entry_id, $original_entry ) { $entry = GFAPI::get_entry( $entry_id ); if ( is_wp_error( $entry ) ) { return; } $status_changed = rgar( $original_entry, '3' ) !== rgar( $entry, '3' ); }, 10, 3 ); // 3, or $original_entry never arrives // Edited in code through GFAPI::update_entry() add_action( 'gform_post_update_entry', function ( $entry, $original_entry ) { $status_changed = rgar( $original_entry, '3' ) !== rgar( $entry, '3' ); }, 10, 2 );
/ Payments
Which hook fires when a payment completes?
gform_post_payment_completed (action, $entry, $action) fires when an add-on built on the Gravity Forms payment framework (GFPaymentAddOn, which the official Stripe and PayPal add-ons use) marks a payment complete. $action carries type, amount, transaction_type, transaction_id, subscription_id, entry_id, payment_status and note. When the gateway confirms the payment after the submission — a redirect checkout or a gateway webhook — gform_after_submission has already fired by then, so act on the payment here instead.
/ Choosing
Which Gravity Forms hook should you use?
| You want to… | Use |
|---|---|
| Fill dropdown or checkbox choices from code | gform_pre_render + gform_pre_validation + gform_pre_submission_filter + gform_admin_pre_render |
| Default one field from a query string or user data | gform_field_value_{name} |
| Reject one field | gform_field_validation_FORMID_FIELDID |
| Reject the submission on a cross-field rule | gform_validation |
| Change a submitted value before it is saved | gform_pre_submission |
| Encrypt a value at rest | gform_save_field_value + gform_get_input_value |
| Change who gets a notification | gform_notification |
| Cancel one email | gform_pre_send_email |
| Redirect based on an answer | gform_confirmation |
| Send the entry to another system | gform_after_submission (check spam status) |
| Mirror edits downstream | gform_after_update_entry + gform_post_update_entry |
| Act only once a payment clears | gform_post_payment_completed |
Sending entries to an external URL is a job of its own — the payload, retries when the endpoint is down, and a log of what was sent. The Gravity Forms webhooks example covers that end to end, and Gravity Forms to n8n and Gravity Forms to Google Sheets show the same hooks feeding real destinations.
Seeing it run beats reading about it. The live preview boots a throwaway WordPress with Webhook Actions already installed and demo deliveries sitting in the log — no signup, nothing left on your machine afterwards.