Developer hooks and WP-CLI for Gravity Forms
Formsieve for Gravity Forms prefixes every hook with fsv_gf_, bundles its own copy of the Formsieve core under the namespace Formsieve\GF\Core\… and adds the WP-CLI command wp formsieve-gf. The examples below are ready to paste into a small plugin or a must-use plugin (wp-content/mu-plugins/). Filters that run when a form is submitted also work from a theme's functions.php, but fsv_gf_booted fires on plugins_loaded, before any theme is loaded, so a listener for it must live in a plugin or mu-plugin.
Filters
| Filter | Arguments | Return |
|---|---|---|
fsv_gf_should_check |
bool $check, Context $ctx |
false skips the check (logged as should_check, delivered). |
fsv_gf_payload |
array $payload (state, questions), Context $ctx |
The request body parts. Invalid values are ignored. |
fsv_gf_thresholds |
array $thresholds (review, block), Context $ctx |
Thresholds between 0 and 1. |
fsv_gf_verdict |
Verdict $verdict, Context $ctx |
A Verdict, before it is logged and applied (other types are ignored). |
fsv_gf_api_base |
string $base_url, string $provider_id |
The base URL, after the FSV_GF_API_BASE constant. |
fsv_gf_api_key |
string $stored_key |
The key. The FSV_GF_API_KEY and FORMSIEVE_API_KEY constants win over this filter. |
fsv_gf_pipeline_stages |
Stage[] $stages |
The ordered pre-filter stages. |
fsv_gf_cli_sample_context |
Context $ctx, int $form_id, string $kind (spam or ham) |
The Context used by wp formsieve-gf test <form_id>; other types are ignored. |
fsv_gf_validation_message |
string $message, Verdict $verdict, Context $ctx |
The message shown when the form's action is Refuse it with a validation error; the result goes through wp_kses_post. |
fsv_gf_tag_notification |
bool $tag, array $notification, array $form, array $entry |
Return false to keep the "[Possible spam NN%]" prefix off one notification. |
formsieve_integrations |
array $integrations |
Shared by every Formsieve plugin: the list on the Formsieve overview page. |
Actions
| Action | Arguments | When |
|---|---|---|
fsv_gf_decided |
Verdict $verdict, Context $ctx |
After every decision has been logged (the verdict carries log_id()). |
fsv_gf_before_request |
Request $request, Context $ctx |
Just before a live API call. |
fsv_gf_feedback |
int $entry_ref, string $label (spam or ham), ?array $log_row |
After an administrator's correction. $entry_ref is the Gravity Forms entry ID; $log_row is the entry's submission row (not a Re-check row), or null when the entry has no Formsieve log row. |
fsv_gf_tested |
array $result |
After Test connection sent its request (not when it was refused for missing consent or key). |
fsv_gf_booted |
Plugin $plugin |
Once the plugin has started, on plugins_loaded. |
fsv_gf_daily |
none | The daily retention task (a cron event). |
formsieve_global_notice |
string $integration_id |
Shared by every Formsieve plugin: after a plugin has printed its one notice outside its own admin page. |
Context (Formsieve\GF\Core\Pipeline\Context) gives you the submission as the integration built it: form_id(), fields(), email_domain(), message(), text(), settings(), setting( $key ), site() and more. It contains visitor data: never log or send it as a whole. Verdict (Formsieve\GF\Core\Classifier\Verdict) offers verdict(), is_allow(), is_review(), is_block(), p(), spam_percent(), category(), reason(), reason_code(), stage(), model(), model_status(), provider(), request_id(), latency_ms(), input_tokens(), cost_micro_usd(), is_checked(), is_demo(), log_id() and with( array $changes ).
Examples
Skip one form entirely:
add_filter( 'fsv_gf_should_check', function ( $check, $ctx ) {
return 12 === $ctx->form_id() ? false : $check;
}, 10, 2 );
Stricter blocking on one form:
add_filter( 'fsv_gf_thresholds', function ( $thresholds, $ctx ) {
if ( 5 === $ctx->form_id() ) {
$thresholds['block'] = 0.95;
}
return $thresholds;
}, 10, 2 );
Never block on one form; send its would-be blocks to review:
add_filter( 'fsv_gf_verdict', function ( $verdict, $ctx ) {
if ( 3 === $ctx->form_id() && $verdict->is_block() ) {
return $verdict->with( array( 'verdict' => 'review' ) );
}
return $verdict;
}, 10, 2 );
Tell the model more about your site (keep visitor text out of everything except state.submission):
add_filter( 'fsv_gf_payload', function ( $payload, $ctx ) {
$payload['state']['site']['description'] .= ' We never buy SEO, marketing or web design services.';
return $payload;
}, 10, 2 );
Changing the questions changes what the thresholds mean; see Calibration.
Log blocked submissions somewhere else:
add_action( 'fsv_gf_decided', function ( $verdict, $ctx ) {
if ( $verdict->is_block() && 'model' === $verdict->stage() ) {
error_log( sprintf( 'Formsieve blocked form %d at %d%% (request %s)', $ctx->form_id(), $verdict->spam_percent(), $verdict->request_id() ) );
}
}, 10, 2 );
Read the key from the environment:
add_filter( 'fsv_gf_api_key', function ( $stored ) {
$key = getenv( 'FORMSIEVE_KEY' );
return $key ? $key : $stored;
} );
Word the validation message your way:
add_filter( 'fsv_gf_validation_message', function ( $message, $verdict, $ctx ) {
return 'Sorry, this message could not be sent. Please call us instead.';
}, 10, 3 );
Never tag the notification that goes to your CRM mailbox:
add_filter( 'fsv_gf_tag_notification', function ( $tag, $notification, $form, $entry ) {
return 'CRM import' === ( $notification['name'] ?? '' ) ? false : $tag;
}, 10, 4 );
Add your own pre-filter stage (it runs first here). The class is declared only when Formsieve for Gravity Forms is active, so the code cannot break the site when the plugin is deactivated:
add_action( 'plugins_loaded', function () {
if ( ! interface_exists( 'Formsieve\GF\Core\Pipeline\Stage' ) ) {
return;
}
final class My_Partner_Allowlist implements \Formsieve\GF\Core\Pipeline\Stage {
public function id(): string {
return 'partner_allowlist';
}
public function run( \Formsieve\GF\Core\Pipeline\Context $ctx, \Formsieve\GF\Core\Pipeline\Result $result ): void {
if ( 'partner.example' === $ctx->email_domain() ) {
$result->decided( 'allow', 'allowlist:partner' );
}
}
}
add_filter( 'fsv_gf_pipeline_stages', function ( $stages ) {
array_unshift( $stages, new My_Partner_Allowlist() );
return $stages;
} );
}, 30 );
WP-CLI
wp formsieve-gf test [<form_id>] [--sample=spam|ham] [--yes] [--format=table|json]
wp formsieve-gf stats [--days=<n>] [--format=table|json]
testwithout a form ID sends the canned Test connection sample with the saved route and key (about 500 tokens) and prints the result; nothing is logged. Like the button, it sends nothing until consent is given for the saved route. It exits with an error when the connection fails, so you can use it in monitoring.test <form_id>builds a synthetic spam (default) or ham (--sample=ham) submission for that form and classifies it exactly like a real one: pre-filters, one API call (or Test mode), decision and one log row, marked as a WP-CLI sample (source=cli). Its API call counts in the API calls, tokens and cost, but the sample is not counted as a submission. Outside Test mode it spends one API call, so it needs--yes. Thecategoryfield of its output is the raw category ID.statsprints decision counts per band, API share, tokens, cost, latency and corrections for the last--daysdays (default 30), plus the month to date and the projection, with the same rules as the dashboard: the decision counts cover visitor submissions only, while API calls, tokens and cost include re-checks and WP-CLI samples.--format=jsonadds the daily breakdown, categories and breaker state.
Data for developers
- Decision log: table
{$wpdb->prefix}fsv_gf_log. Numbers and identifiers only (times in UTC); read it, but do not write to it. Thesourcecolumn tells a visitor's submission (submission) from an administrator's Re-check (recheck) and a WP-CLI sample (cli); onlysubmissionrows count as submissions on the dashboard.categoryholds the raw ID (for examplevendor_solicitation);\Formsieve\GF\Core\Admin\Format::category( $id )returns the translated label the screens show. - Options:
fsv_gf_settings,fsv_gf_lists(the allow and block lists, not autoloaded),fsv_gf_route,fsv_gf_api_key(obfuscated, never read it directly) and more, all prefixedfsv_gf_. - Gravity Forms entry meta:
fsv_score,fsv_verdict,fsv_category,fsv_reason,fsv_model,fsv_model_status,fsv_provider,fsv_latency_ms,fsv_request_id,fsv_request_id_kind. - REST (both require
manage_optionsand thewp_restnonce):POST /wp-json/formsieve-gf/v1/test-connectionbacks the admin's Test connection button. It sends nothing until consent covers the tested route (error_codeno_consent,sent: false), and it sends the saved key only to the saved route (no_keyotherwise; type the key to test another route).POST /wp-json/formsieve-gf/v1/log/<id>/labelwithlabel=spamorhamrecords an administrator's correction for one log row, as the Log tab does.