WP Webhooks / Blog / Gravity Forms

Gravity Forms Hooks: Developer Reference & Examples

Every Gravity Forms hook you will actually use — render, validation, submission, notification, entry-edit and payment — with arguments, firing order and examples.

11 min 2026-06-18 Updated 2026-10-14
#gravity-forms#wordpress#webhooks

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_5 runs 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_entry for the wp-admin entry screen, gform_post_update_entry for GFAPI::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.

FIG 01 — Gravity Forms submission hook order (verified in GFFormDisplay::process_form)

/ 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.

HookTypeFiresArguments
gform_pre_renderFilterBefore the form is displayed$form, $ajax, $field_values, $context
gform_field_value_{name}FilterPer field, while populating defaults$value, $field, $name
gform_submit_buttonFilterWhile rendering the form footer$button_input, $form
gform_post_render (JS)jQuery eventEvery render, incl. multi-page stepsevent, form_id, current_page
gform_pre_validationFilterBefore validation runs$form
gform_field_validationFilterOn submit, once per field$result, $value, $form, $field, $context
gform_validationFilterOn submit, for the whole form$validation_result, $context
gform_validation_messageFilterWhen validation fails$message, $form
gform_pre_submissionActionAfter validation, before the entry is saved$form
gform_pre_submission_filterFilterRight after gform_pre_submission$form
gform_save_field_valueFilterPer input, as the value is saved$value, $entry, $field, $form, $input_id
gform_entry_createdActionEntry saved, before notifications$entry, $form
gform_entry_post_saveFilterRight after gform_entry_created$entry, $form
gform_notificationFilterPer notification, before it becomes an email$notification, $form, $entry
gform_pre_send_emailFilterJust before wp_mail()$email, $message_format, $notification, $entry
gform_after_emailActionAfter wp_mail() returns$is_success, $to, $subject, … $entry
gform_confirmationFilterWhen the confirmation is built$confirmation, $form, $entry, $is_ajax
gform_after_submissionActionLast step of the submission$entry, $form
gform_after_update_entryActionEntry edited on the wp-admin entry screen$form, $entry_id, $original_entry
gform_post_update_entryActionEntry edited via GFAPI::update_entry()$entry, $original_entry
gform_post_payment_completedActionA 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 );
Cyberpunk illustration titled “Gravity Forms Hooks — Developer Reference & Examples” showing a developer at a holographic console beside a glowing “Gravity Forms Hooks” database cylinder, labelled with apply_filters(), do_action(), gform_pre_render and gform_after_submission.

/ 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 codegform_pre_render + gform_pre_validation + gform_pre_submission_filter + gform_admin_pre_render
Default one field from a query string or user datagform_field_value_{name}
Reject one fieldgform_field_validation_FORMID_FIELDID
Reject the submission on a cross-field rulegform_validation
Change a submitted value before it is savedgform_pre_submission
Encrypt a value at restgform_save_field_value + gform_get_input_value
Change who gets a notificationgform_notification
Cancel one emailgform_pre_send_email
Redirect based on an answergform_confirmation
Send the entry to another systemgform_after_submission (check spam status)
Mirror edits downstreamgform_after_update_entry + gform_post_update_entry
Act only once a payment clearsgform_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.

try_it

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.

FAQ

Common questions always ask.

Don't see yours? Open an issue on GitHub or check the full reference in the API docs.

What are Gravity Forms hooks? +
Gravity Forms hooks are the WordPress actions and filters the plugin fires throughout a form lifecycle — render, validation, submission, entry storage, notifications, confirmation and later entry edits. You attach a callback with add_action() or add_filter() to run custom logic at that moment without editing the plugin.
What is the difference between Gravity Forms actions and filters? +
Actions let you do something when an event fires and return nothing — gform_after_submission hands you the saved entry. Filters let you modify a value and must return it — gform_pre_render hands you the form array and expects it back. Forgetting to return inside a filter blanks out the value Gravity Forms was about to use.
In what order do Gravity Forms submission hooks fire? +
gform_pre_validation, gform_field_validation (per field), gform_validation, then gform_pre_submission and gform_pre_submission_filter. The entry is saved (gform_save_field_value per input), then gform_entry_created and gform_entry_post_save fire. Notifications follow (gform_notification, gform_pre_send_email, gform_after_email), then gform_confirmation, and gform_after_submission fires last.
What is the difference between gform_after_submission and gform_entry_created? +
gform_entry_created fires right after the entry is saved, before notifications are sent and the confirmation is processed. gform_after_submission fires at the very end of the submission, after notifications and the confirmation. Both receive $entry and $form.
Does gform_after_submission run for spam entries? +
Yes. The official documentation notes that gform_after_submission also runs for entries marked as spam, while notifications are skipped for them. Check rgar( $entry, 'status' ) === 'spam' at the top of your callback before sending the entry anywhere.
Can I stop the form submission from inside gform_after_submission? +
No. gform_after_submission fires after the entry has been saved and notifications sent. To block a submission, fail it in gform_validation or gform_field_validation — a failed validation means no entry, no notifications and no gform_after_submission.
How do I access a specific field value inside gform_after_submission? +
Use rgar($entry, $field_id) where $field_id is the numeric Gravity Forms field ID as a string. For example, rgar($entry, '1') returns the value of field 1. For multi-input fields like Name or Address, use the input ID: rgar($entry, '1.3') returns the first name of a Name field with ID 1. rgar() returns an empty string for a missing key instead of a PHP notice.
What parameters does gform_after_update_entry pass? +
Three: $form (the form array), $entry_id (the integer ID of the edited entry) and $original_entry (the entry before the edit, available since Gravity Forms 1.9.12.9). Register it with 3 accepted arguments to receive $original_entry, and load the updated entry with GFAPI::get_entry($entry_id).
Does gform_after_update_entry fire when an entry is updated with GFAPI::update_entry()? +
No. gform_after_update_entry fires only after an entry is saved on the wp-admin entry detail screen. Edits made through GFAPI::update_entry() fire gform_post_update_entry, which passes the updated $entry and $original_entry. To react to every edit, hook both.
What replaced gform_post_render in Gravity Forms 2.9? +
The native JavaScript event gform/post_render, added in Gravity Forms 2.9.0. Listen with document.addEventListener('gform/post_render', ...) and read event.detail.formId and event.detail.currentPage.
How do I make a Gravity Forms hook run for only one form? +
Append the form ID to the hook name: gform_after_submission_5 runs only for form 5. Field-level hooks such as gform_field_validation also accept a field ID: gform_field_validation_5_3 runs only for field 3 of form 5.
Where is the official Gravity Forms hooks documentation? +
The canonical list lives at docs.gravityforms.com under the developers/hooks category. Each hook has its own reference page that labels it as an action or a filter and documents its parameters.
Ready

Your next automation is
one sentence away.

$ wp plugin install flowsystems-webhook-actions --activate