PHP Hook Reference
Form Plant provides WordPress action / filter hooks at almost every stage of form behavior — submission processing, email sending, redirects, file uploads, fields, form settings, and more. Just add a hook in functions.php ( or similar ) to extend and customize behavior without modifying the plugin itself.
Hooks for input validation ( fplant_validate_* / fplant_validation_message_*, etc. ) are collected on Validation Hooks (PHP). This page covers everything else.
A rewrite mapping table from mwform_* hooks, with before/after examples, is available in Migrating from MW WP Form. Please refer to it as well.
Hook basics
Targeting a specific form
Many hooks fire for all forms on the site. If you want to target only a specific form, check the form ID ( $form_id ) inside your callback. The form ID is the number in the placement shortcode [fplant id="12"] or in the editor URL ( ...&id=12 ) .
add_filter( 'fplant_redirect_url', function ( $url, $form_id, $data ) {
if ( 12 !== $form_id ) {
return $url; // Return as-is for forms other than the target
}
return home_url( '/thanks/' );
}, 10, 3 );
Hooks targeted by field name (dynamic hooks)
Some hooks let you append a field name to target only a specific field ( e.g. fplant_field_choices_{name} ). Internally these are called in two stages, "common across all fields → per-field version," and the per-field version receives the return value of the common version.
- Common version:
fplant_field_choices… includes$field_namein its arguments - Per-field version: embed the name, as in
fplant_field_choices_inquiry_type
The field name used in hook names and callbacks is the name ( alphanumeric key ) you can check on the form editor screen. If you migrated from MW WP Form, Japanese names have been converted to alphanumeric ( e.g. お名前 → your_name ) .
Form display / submission control
Since v1.2.1, forms respect the post status. Published forms can be viewed and submitted by anyone, while private, draft, and pending-review forms are not shown to or submittable by general visitors — only users who can edit the form can preview and test-submit them. You can override this decision with the following filters.
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_form_is_viewable | filter | ( $viewable, $form ) | Whether the form may be displayed on the front end 🆕 |
fplant_form_is_submittable | filter | ( $submittable, $form ) | Whether the form accepts submissions 🆕 |
fplant_preview_notice | filter | ( $notice, $form ) | The HTML of the preview notice shown above an unpublished form 🆕 |
$form is a form array that includes id and status. Returning true allows display/submission regardless of status.
When a user with edit permission views an unpublished form, a preview notice reading "This form is not published" appears at the top. You can replace the text with fplant_preview_notice or return an empty string to hide it. This is useful for cases like members-only forms ( where fplant_form_is_viewable allows display to members ) , where the form is shown to non-editors too.
Example: revert to allowing all statuses to be viewed/submitted as before
add_filter( 'fplant_form_is_viewable', '__return_true' );
add_filter( 'fplant_form_is_submittable', '__return_true' );
Example: display only a specific form even when unpublished
add_filter( 'fplant_form_is_viewable', function ( $viewable, $form ) {
if ( 12 === (int) $form['id'] ) {
return true;
}
return $viewable;
}, 10, 2 );
Example: members-only form ( show an unpublished form only to logged-in members )
This example lets logged-in members view and submit an unpublished form, and hides the editor-facing preview notice.
// Allow form ID 12 to be viewed/submitted by anyone who is logged in
add_filter( 'fplant_form_is_viewable', function ( $viewable, $form ) {
return ( 12 === (int) $form['id'] && is_user_logged_in() ) ? true : $viewable;
}, 10, 2 );
add_filter( 'fplant_form_is_submittable', function ( $submittable, $form ) {
return ( 12 === (int) $form['id'] && is_user_logged_in() ) ? true : $submittable;
}, 10, 2 );
// Do not show the "unpublished" preview notice to members
add_filter( 'fplant_preview_notice', function ( $notice, $form ) {
return ( 12 === (int) $form['id'] ) ? '' : $notice;
}, 10, 2 );
Submission lifecycle
A form submission is processed in the following order. Hooks are provided at each stage. They are color-coded as ■ action / ■ filter.
| Hook | Type | Arguments | Timing / purpose |
|---|---|---|---|
fplant_before_submission | action | ( $form_id, $data ) | Before validation. For logging or preprocessing raw data |
fplant_submission_data | filter | ( $data, $form_id ) | Modify data right after sanitization. Return value = the modified data |
fplant_before_save_submission_data | filter | ( $data, $form_id ) | Modify data right before saving to the DB ( before password masking ) . Also useful for unset-ing specific keys to exclude them from saving. Return value = the data to save |
fplant_after_submission | action | ( $submission_id, $form_id, $data ) | After saving to the DB, before sending emails |
fplant_after_submission_complete | action | ( $data, $form_id, $form, $submission_id ) | After emails are sent. The go-to hook for integrating with external services |
Example: integrate with an external service after submission completes
add_action( 'fplant_after_submission_complete', function ( $data, $form_id, $form, $submission_id ) {
if ( 12 !== $form_id ) {
return;
}
// e.g. notify Slack or a CRM
wp_remote_post( 'https://example.com/webhook', array(
'body' => array(
'submission_id' => $submission_id,
'email' => $data['email'] ?? '',
),
'timeout' => 5,
) );
}, 10, 4 );
Example: do not save a specific field to the DB
add_filter( 'fplant_before_save_submission_data', function ( $data, $form_id ) {
// Exclude fields you don't want to store, such as a credit card number
unset( $data['card_number'] );
return $data;
}, 10, 2 );
Post-submission actions (completion message / redirect)
Depending on the "Post-submission action" in the form settings, exactly one of these fires. All were added in v1.2.0.
| Hook | Type | Arguments | Setting that enables it |
|---|---|---|---|
fplant_complete_message | filter | ( $message, $form_id, $data ) | "Message" |
fplant_success_html | filter | ( $html, $form_id, $data ) | "Custom page" ( the HTML after placeholder replacement ) |
fplant_redirect_url | filter | ( $url, $form_id, $data ) | "Redirect" ( before esc_url_raw ) |
Example: change the completion message based on input values
add_filter( 'fplant_complete_message', function ( $message, $form_id, $data ) {
if ( 12 === $form_id ) {
$name = $data['your_name'] ?? 'Customer';
$message = 'Dear ' . $name . ', thank you for your inquiry.';
}
return $message;
}, 10, 3 );
Example: branch the redirect target by plan
add_filter( 'fplant_redirect_url', function ( $url, $form_id, $data ) {
if ( 12 === $form_id && 'premium' === ( $data['plan'] ?? '' ) ) {
$url = home_url( '/thanks-premium/' );
}
return $url;
}, 10, 3 );
Email sending
Paired hooks are provided for the admin email and the auto-reply ( user ) email. Replacing admin with user gives you the auto-reply side.
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_before_admin_email_send | action | ( $email_settings, $form_id ) | At the start of admin email processing |
fplant_skip_admin_email | filter | ( $skip, $form, $data, $submission_id ) | Return true to skip sending 🆕 |
fplant_before_send_email_data | filter | ( $data, $form, $submission_id, $type ) | Modify data before generating the body. $type is 'admin' / 'user' ( shared hook ) |
fplant_admin_email_to | filter | ( $to, $form_id, $data ) | The admin recipient ( array ) . An empty array skips sending 🆕 |
fplant_admin_email_subject | filter | ( $subject, $form_id ) | Subject ( no 3rd argument ) |
fplant_admin_email_body | filter | ( $message, $form_id, $data ) | Body |
fplant_admin_email_headers | filter | ( $headers, $form_id, $data ) | Headers ( From / Cc / Bcc / Reply-To ) 🆕 |
fplant_after_admin_email_send | action | ( $email_settings, $form_id, $result ) | After wp_mail() runs. $result is success/failure |
Auto-reply side: fplant_before_user_email_send / fplant_skip_user_email 🆕 / fplant_user_email_to 🆕 / fplant_user_email_subject / fplant_user_email_body / fplant_user_email_headers 🆕 / fplant_after_user_email_send.
🆕 = added in v1.2.0.
The admin recipient ( fplant_admin_email_to ) returns an array, assuming multiple recipients, while the auto-reply recipient ( fplant_user_email_to ) returns a single string. The 2nd argument $form of fplant_skip_admin_email / fplant_skip_user_email is the form array, not the form ID ( get the ID with $form['id'] ) .
Example: route the admin email recipient based on input
add_filter( 'fplant_admin_email_to', function ( $to, $form_id, $data ) {
if ( 12 === $form_id && 'sales' === ( $data['category'] ?? '' ) ) {
$to = array( 'sales@example.com' );
}
return $to;
}, 10, 3 );
Example: add a BCC
add_filter( 'fplant_admin_email_headers', function ( $headers, $form_id, $data ) {
if ( 12 === $form_id ) {
$headers[] = 'Bcc: archive@example.com';
}
return $headers;
}, 10, 3 );
Example: do not send the auto-reply under certain conditions
add_filter( 'fplant_skip_user_email', function ( $skip, $form, $data, $submission_id ) {
if ( (int) $form['id'] === 12 && empty( $data['newsletter'] ) ) {
return true; // Cancel sending
}
return $skip;
}, 10, 4 );
File uploads
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_upload_dir | filter | ( $custom_dir, $form_id ) | The destination directory. $custom_dir is array( 'path' => ..., 'url' => ... ) 🆕 |
fplant_upload_filename | filter | ( $filename, $field_config, $form_id ) | The saved file name 🆕 |
The return value of fplant_upload_dir is restricted to within the WordPress uploads folder ( specifying outside it causes an error ) . The return value of fplant_upload_filename is re-sanitized, and dangerous extensions ( .php, etc. ) are rejected. These hooks are for organization ( date-based folders, naming conventions ) and cannot disable the protection mechanisms.
Example: save into year/month folders
add_filter( 'fplant_upload_dir', function ( $dir, $form_id ) {
$u = wp_upload_dir();
$sub = '/form-uploads/' . gmdate( 'Y-m' );
return array(
'path' => $u['basedir'] . $sub,
'url' => $u['baseurl'] . $sub,
);
}, 10, 2 );
Fields, choices, initial values
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_field_types | filter | ( $field_types ) | Add or change the definitions of available field types |
fplant_field_defaults | filter | ( $defaults, $field_type ) | Default settings per field type |
fplant_field_initial_value | filter | ( null, $field_name, $field, $form_id ) | A common initial value for all fields. Returning null passes to the next |
fplant_field_initial_value_{name} | filter | ( $value, $field, $form_id ) | A per-field initial value |
fplant_field_choices | filter | ( $options, $field_name, $field, $form_id ) | Common choices for all fields ( select / radio / checkbox ) 🆕 |
fplant_field_choices_{name} | filter | ( $options, $field, $form_id ) | Per-field choices 🆕 |
fplant_custom_mail_tag_value | filter | ( '', $field_name, $field, $form_id ) | The value of a custom mail tag ( common version ) 🆕 |
fplant_custom_mail_tag_value_{name} | filter | ( $value, $field, $form_id ) | The value of a custom mail tag ( per-field ) 🆕 |
Choices ( $options ) are an array of array( 'label' => display name, 'value' => value ).
Example: use the logged-in user's email as the initial value
add_filter( 'fplant_field_initial_value_email', function ( $value, $field, $form_id ) {
if ( is_user_logged_in() ) {
$value = wp_get_current_user()->user_email;
}
return $value;
}, 10, 3 );
Example: dynamically generate select choices from a post list
add_filter( 'fplant_field_choices_product', function ( $options, $field, $form_id ) {
foreach ( get_posts( array( 'post_type' => 'product' ) ) as $p ) {
$options[] = array( 'label' => $p->post_title, 'value' => (string) $p->ID );
}
return $options;
}, 10, 3 );
Example: inject the member rank into a custom mail tag
This supplies the value of a custom_mail_tag field ( name = member_rank ) via a hook.
add_filter( 'fplant_custom_mail_tag_value_member_rank', function ( $value, $field, $form_id ) {
return get_user_meta( get_current_user_id(), 'rank', true );
}, 10, 3 );
Extending form settings
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_custom_settings_fields | filter | ( $fields, $form_id ) | Add your own input fields to the form settings screen 🆕 |
fplant_form_settings_saved | action | ( $form_id, $settings ) | Right after the form settings are saved 🆕 |
Fields defined with fplant_custom_settings_fields are automatically rendered and saved on the settings screen, and can be referenced at runtime via $form['settings'][key].
Example: add a CRM endpoint URL input to the settings screen
add_filter( 'fplant_custom_settings_fields', function ( $fields, $form_id ) {
$fields[] = array(
'key' => 'x_crm_endpoint', // alphanumeric / underscore
'type' => 'text', // text / textarea / checkbox / select / number
'label' => 'CRM endpoint URL',
'description' => 'The CRM API endpoint',
);
return $fields;
}, 10, 2 );
// Use the saved value when submission completes
add_action( 'fplant_after_submission_complete', function ( $data, $form_id, $form, $submission_id ) {
if ( ! empty( $form['settings']['x_crm_endpoint'] ) ) {
// Processing that uses $form['settings']['x_crm_endpoint']
}
}, 10, 4 );
Templates / rendering
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_locate_template | filter | ( $template, $template_name, $template_path ) | Change the resolved path of a template file ( e.g. override on the theme side ) |
fplant_allowed_field_types | filter | ( $allowed_field_types ) | The whitelist of field types allowed in HTML templates |
fplant_template_values | filter | ( $values, $form_id ) | Inject values into {{key}} placeholders in an HTML template ( values are esc_html-escaped ) |
For how to use the HTML template feature itself, see HTML Template.
Example: inject a dynamic value into a template
add_filter( 'fplant_template_values', function ( $values, $form_id ) {
$values['today'] = wp_date( 'F j, Y' );
return $values;
}, 10, 2 );
// Writing {{today}} in the template replaces it with today's date
Data export
| Hook | Type | Arguments | Purpose |
|---|---|---|---|
fplant_export_encoding | filter | ( $encoding, $form_id ) | The character encoding of the submission data CSV ( default UTF-8 ) 🆕 |
Example: output as Shift-JIS for Excel compatibility
add_filter( 'fplant_export_encoding', function ( $encoding, $form_id ) {
return 'sjis-win'; // Shift-JIS ( CP932 )
}, 10, 2 );
Hook list (quick reference)
🆕 = added in v1.2.0 / v1.2.1.
| Hook | Type | Arguments |
|---|---|---|
fplant_form_is_viewable 🆕 | filter | ( $viewable, $form ) |
fplant_form_is_submittable 🆕 | filter | ( $submittable, $form ) |
fplant_preview_notice 🆕 | filter | ( $notice, $form ) |
fplant_before_submission | action | ( $form_id, $data ) |
fplant_submission_data | filter | ( $data, $form_id ) |
fplant_before_save_submission_data | filter | ( $data, $form_id ) |
fplant_after_submission | action | ( $submission_id, $form_id, $data ) |
fplant_after_submission_complete | action | ( $data, $form_id, $form, $submission_id ) |
fplant_complete_message 🆕 | filter | ( $message, $form_id, $data ) |
fplant_success_html 🆕 | filter | ( $html, $form_id, $data ) |
fplant_redirect_url 🆕 | filter | ( $url, $form_id, $data ) |
fplant_before_admin_email_send | action | ( $email_settings, $form_id ) |
fplant_skip_admin_email 🆕 | filter | ( $skip, $form, $data, $submission_id ) |
fplant_before_send_email_data | filter | ( $data, $form, $submission_id, $type ) |
fplant_admin_email_to 🆕 | filter | ( $to, $form_id, $data ) |
fplant_admin_email_subject | filter | ( $subject, $form_id ) |
fplant_admin_email_body | filter | ( $message, $form_id, $data ) |
fplant_admin_email_headers 🆕 | filter | ( $headers, $form_id, $data ) |
fplant_after_admin_email_send | action | ( $email_settings, $form_id, $result ) |
fplant_before_user_email_send | action | ( $email_settings, $form_id ) |
fplant_skip_user_email 🆕 | filter | ( $skip, $form, $data, $submission_id ) |
fplant_user_email_to 🆕 | filter | ( $to, $form_id, $data ) |
fplant_user_email_subject | filter | ( $subject, $form_id ) |
fplant_user_email_body | filter | ( $message, $form_id, $data ) |
fplant_user_email_headers 🆕 | filter | ( $headers, $form_id, $data ) |
fplant_after_user_email_send | action | ( $email_settings, $form_id, $result ) |
fplant_upload_dir 🆕 | filter | ( $custom_dir, $form_id ) |
fplant_upload_filename 🆕 | filter | ( $filename, $field_config, $form_id ) |
fplant_field_types | filter | ( $field_types ) |
fplant_field_defaults | filter | ( $defaults, $field_type ) |
fplant_field_initial_value ( _{name} ) | filter | ( $value, [$field_name,] $field, $form_id ) |
fplant_field_choices 🆕 ( _{name} ) | filter | ( $options, [$field_name,] $field, $form_id ) |
fplant_custom_mail_tag_value 🆕 ( _{name} ) | filter | ( $value, [$field_name,] $field, $form_id ) |
fplant_custom_settings_fields 🆕 | filter | ( $fields, $form_id ) |
fplant_form_settings_saved 🆕 | action | ( $form_id, $settings ) |
fplant_locate_template | filter | ( $template, $template_name, $template_path ) |
fplant_allowed_field_types | filter | ( $allowed_field_types ) |
fplant_template_values | filter | ( $values, $form_id ) |
fplant_export_encoding 🆕 | filter | ( $encoding, $form_id ) |
For validation hooks (
fplant_validate_*/fplant_validation_message_*/fplant_before_validation/fplant_validation_errors), see Validation Hooks (PHP).
Security notes
- Always escape input received through hooks with
esc_html()/esc_attr()/esc_url(), etc. before output. - For processes that call external APIs, set a
timeoutand cache with the Transient API as needed. - The protection mechanisms of
fplant_upload_dir/fplant_upload_filename( restricted to under uploads, extension blacklist ) cannot be disabled.