TL;DR
- Gravity Forms exposes its lifecycle through actions (do something when an event fires) and filters (modify a value before the plugin uses it).
- The hooks you reach for most:
gform_pre_submission,gform_validation,gform_after_submission,gform_after_update_entry, and the field filtersgform_pre_renderandgform_field_value_*. - To fire a webhook on submit, hook
gform_after_submission— it runs once the entry is saved, so you have the entry ID and all field values. - The Webhook Actions plugin binds to these hooks from the admin UI, so you get async delivery, retries, and logs without writing the dispatch code yourself.
/ 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, notification, and confirmation. You attach a callback with add_action() or add_filter() and run your own logic at exactly the right moment, without editing the plugin.
There are hundreds of them. In practice a handful cover almost every integration: validate input, modify a field before it renders, react after an entry is saved, and react when an entry is later edited. Everything below is organized by where it fires in the lifecycle, with the canonical Gravity Forms documentation linked for each¹.
User submits form
│
▼
gform_field_validation / gform_validation ← reject or accept input
│ (valid)
▼
gform_pre_submission ← last chance to mutate $_POST
│
▼
Entry written to database
│
▼
gform_after_submission( $entry, $form ) ← entry ID + all field values
│
├─▶ gform_after_email / notifications
└─▶ gform_confirmation ← what the user sees next / 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 so you can send it somewhere. A filter callback receives a value and must return it (modified or not) — gform_pre_render hands you the form array and expects the form array back.
The practical rule: if your callback forgets to return inside a filter, you will blank out the value Gravity Forms was about to use. Actions have no such trap. When in doubt, the docs label each hook as an action or a filter at the top of its page².
| Hook | Type | Fires when | Use it for |
|---|---|---|---|
| gform_pre_render | Filter | Before the form is rendered | Inject choices, hide fields, set defaults |
| gform_field_validation | Filter | Per field, on submit | Reject a single field |
| gform_validation | Filter | On submit, across all fields | Reject a submission with custom logic |
| gform_pre_submission | Action | After validation, before save | Mutate $_POST values |
| gform_after_submission | Action | After the entry is saved | Webhooks, CRM sync, analytics |
| gform_after_update_entry | Action | An existing entry is edited | Propagate edits downstream |
| gform_confirmation | Filter | After save, before display | Change the confirmation or redirect |
| gform_pre_send_email | Filter | Before a notification is sent | Rewrite or abort the email |
/ Submission
Which hooks fire when a form is submitted?
Three matter most, in order. gform_pre_submission runs after validation passes but before the entry is saved — the last point you can mutate $_POST['input_5'] values. gform_after_submission runs once the entry exists in the database, giving you the complete $entry array (including $entry['id']) and the $form definition. gform_after_email fires after notifications are sent.
Use gform_after_submission for almost all outbound integrations — CRM sync, webhook dispatch, analytics — because the entry is persisted and has a stable ID to deduplicate on.
PHP — react after a submission is saved
// Fires once the entry is in the database. $entry has the ID + all fields. add_action( 'gform_after_submission', function( $entry, $form ) { $email = rgar( $entry, '3' ); // field ID 3 $entry_id = $entry['id']; // hand off to your integration here }, 10, 2 );
/ Validation
How do you validate submissions with gform_validation?
Use gform_validation to reject a submission server-side based on logic Gravity Forms cannot express in the field settings — a remote API check, a duplicate-entry lookup, a business rule across multiple fields. The filter hands you a $validation_result array; set ['is_valid'] = false, mark the offending field's failed_validation, and return the array.
For single-field checks, gform_field_validation is narrower and simpler. Both run before the entry is saved, so a failed validation means no entry, no notifications, and no gform_after_submission — which is exactly why validation is the right place to stop bad data, not a post-submission webhook.
Validation hooks run before the entry is saved — so rejecting bad input there means it never reaches your downstream systems in the first place.
/ Fields
How do you change field values and choices before render?
gform_pre_render is the filter for modifying a form on the way to the screen — inject choices into a dropdown, hide a field, or change a default. It receives the $form array and must return it. For pre-populating a single field from a query string or dynamic source, the field-specific gform_field_value_{parameter_name} filter is more precise and avoids rebuilding the whole form array³.
Both are filters, so the cardinal rule applies: always return the value. A common mistake is calling gform_pre_render to read the form and forgetting to return it, which renders an empty form.
/ Entries
Which hooks fire when an entry is created or updated?
gform_after_submission covers new entries. For edits, gform_after_update_entry fires when an existing entry is changed from the wp-admin entry editor or via the GFAPI — and it hands you both the updated entry and the $original_entry, so you can diff fields and only act on real changes. This pairing is what lets you keep a downstream CRM mirrored rather than just snapshotting it at submit time.
We cover both in depth: the gform_after_submission webhook guide for new submissions, and the gform_after_update_entry guide for edits and deduplication.
/ Notifications
How do you customize confirmations and notifications?
gform_confirmation filters what the user sees after submitting — swap the confirmation message, change the redirect URL conditionally, or return a different confirmation per logic branch. gform_pre_send_email filters the notification email immediately before it is sent, letting you rewrite recipients, subject, or body, or abort the send entirely by setting $email['abort_email'] = true.
Both are filters and both fire after the entry is saved, so they are safe places to read final entry values but the wrong place to do outbound integration work — use gform_after_submission for that.
/ Webhooks
Which hook should you use to fire a webhook?
Use gform_after_submission for new submissions and gform_after_update_entry for edits. Both fire after the entry is persisted, so the payload has a stable entry ID and complete field values — everything a receiving system needs to deduplicate and process the event. Firing from a pre-save hook risks sending data for a submission that later fails validation.
The raw approach is a hook callback that builds a payload and calls wp_remote_post(). That works until the endpoint is slow (your form submit blocks on it), returns a 500 (you have no retry), or silently changes (you have no log). The Gravity Forms to n8n guide walks through the full pattern.
| Capability | Raw hook + wp_remote_post | Webhook Actions |
|---|---|---|
| Setup | Custom PHP per form and hook | Admin UI trigger picker — no code |
| Timing | Synchronous — blocks the form submit | Async queue — submit returns immediately |
| Retry | None — one attempt only | Exponential backoff, default 5 attempts |
| Logs | None — failures are invisible | Per-attempt log with status + response body |
| Replay | Not possible without custom tooling | One-click replay from the Logs screen |
The Webhook Actions plugin binds to gform_after_submission and gform_after_update_entry from the admin UI. Pick the form, point it at an endpoint, and delivery runs through a background queue with smart retry and exponential backoff (~30s, 60s, 120s, 240s, 480s, capped at 1 hour; default 5 attempts) and a full delivery log — no dispatch code on your side.