# Contact Filters
These filter hooks let you customize contact data, profile sections, custom fields, avatars, smart codes, bulk actions, and more.
## Contact Statuses & Types
### `fluent_crm/contact_statuses`
Filter the list of valid contact subscription statuses.
**Parameters**
- `$statuses` Array - a plain list of status slugs. Defaults to `subscribed`, `pending`, `unsubscribed`, `transactional`, `bounced`, `complained`, `spammed`
**Usage:**
```php
add_filter('fluent_crm/contact_statuses', function($statuses) {
// Add a custom status
$statuses[] = 'on_hold';
return $statuses;
});
```
**Source:** `app/Functions/helpers.php`
---
### `fluent_crm/contact_editable_statuses`
Filter which contact statuses are user-editable (can be set manually by an admin). The incoming list
is `fluent_crm/contact_statuses` with the three system-only statuses — `bounced`, `complained` and
`spammed` — already removed.
::: tip
Adding a status here only affects the editable list. Register it on
[`fluent_crm/contact_statuses`](#fluent-crm-contact-statuses) as well, or the rest of the CRM will
not recognise it.
:::
**Parameters**
- `$statuses` Array - the status list minus `bounced`, `complained` and `spammed`. Array keys are preserved from `array_diff()`, so the list is not re-indexed.
**Usage:**
```php
add_filter('fluent_crm/contact_editable_statuses', function($statuses) {
// Remove a status from the editable list
return array_diff($statuses, ['complained']);
});
```
**Source:** `app/Functions/helpers.php`
---
### `fluent_crm/contact_types`
Filter the contact type definitions (e.g., Lead, Customer).
**Parameters**
- `$types` Array - Associative array of `slug => label`
**Usage:**
```php
add_filter('fluent_crm/contact_types', function($types) {
$types['partner'] = __('Partner', 'fluent-crm');
return $types;
});
```
**Source:** `app/Functions/helpers.php`
---
### `fluent_crm/contact_activity_types`
Filter the activity/timeline event type definitions shown on the contact profile.
**Parameters**
- `$types` Array - Associative array of `slug => label` (e.g., `note`, `call`, `email`, `meeting`)
**Usage:**
```php
add_filter('fluent_crm/contact_activity_types', function($types) {
$types['sms'] = __('SMS', 'fluent-crm');
return $types;
});
```
**Source:** `app/Functions/helpers.php`
---
### `fluent_crm/status_text`
Filter the mapping of internal status keys to human-readable labels.
**Parameters**
- `$mapStatus` Array - Associative array of `status_key => display_label`
**Usage:**
```php
add_filter('fluent_crm/status_text', function($mapStatus) {
$mapStatus['subscribed'] = 'Active Member';
return $mapStatus;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluent_crm/contact_name_prefixes`
Filter the name prefix/salutation options (Mr, Mrs, Ms, etc.). This is the current hook (since 2.7.0).
> **Note:** A legacy hook `fluentcrm_contact_name_prefixes` also exists (since 2.5.5) and fires first. The current hook applies on top of it.
**Parameters**
- `$prefixes` Array - List of prefix strings
**Usage:**
```php
add_filter('fluent_crm/contact_name_prefixes', function($prefixes) {
$prefixes[] = 'Dr';
$prefixes[] = 'Prof';
return $prefixes;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluent_crm/email_sendable_statuses`
Filter which contact statuses are eligible to receive campaign emails.
**Parameters**
- `$statuses` Array - Default: `['subscribed', 'transactional']`
**Usage:**
```php
add_filter('fluent_crm/email_sendable_statuses', function($statuses) {
// Only send to subscribed contacts
return ['subscribed'];
});
```
**Source:** `app/Functions/helpers.php`
---
## Profile Sections & Widgets
### `fluentcrm_profile_sections`
Filter the array of tab sections displayed on the contact profile page. Use this to add custom tabs.
**Parameters**
- `$sections` Array - Each section has `slug`, `title`, `icon`, and other properties
**Usage:**
```php
add_filter('fluentcrm_profile_sections', function($sections) {
$sections['my_custom_tab'] = [
'slug' => 'my_custom_tab',
'title' => __('My Tab', 'fluent-crm'),
'icon' => 'el-icon-setting'
];
return $sections;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluent_crm/subscriber_top_widgets`
Filter the array of "top" widgets shown above the timeline on a contact profile. The incoming array
holds the built-in commerce widget when one applies, and the returned array is passed through
`array_filter()`, so returning a falsy entry removes that widget.
::: danger Escape your widget content
Each widget's `content` is rendered as raw HTML in the admin UI (Vue `v-html`) with no client-side
sanitization. Escape any contact-authored data with `esc_html()` or `wp_kses_post()` before returning
it, or you introduce stored XSS in the admin. Rich markup is allowed by design — the responsibility
sits with the code producing the widget.
:::
**Parameters**
- `$widgets` Array - widget definitions, each with `title` and `content`
- `$subscriber` [Subscriber Model](/database/models/subscriber)
**Usage:**
```php
add_filter('fluent_crm/subscriber_top_widgets', function($widgets, $subscriber) {
$widgets[] = [
'title' => 'Custom Widget',
'content' => '
Custom content for ' . $subscriber->email . '
'
];
return $widgets;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php`
---
### `fluent_crm/subscriber_info_widgets`
Filter additional sidebar info widgets on a contact profile page. Starts as an empty array. Also
consulted when the MCP contact tools assemble a contact profile. A widget may additionally set
`has_pagination`, `total`, `per_page` and `current_page` keys — the profile sidebar then re-fetches
that single widget page by page through
[`fluent_crm/subscriber_info_widget_{$widgetKey}`](#fluent-crm-subscriber-info-widget-widgetkey).
::: danger Escape your widget content
Same as `fluent_crm/subscriber_top_widgets` — `content` is rendered with Vue `v-html` and is not
sanitized. Escape contact-authored data before returning it.
:::
**Parameters**
- `$widgets` Array - widget definitions, each with `title` and `content`; empty by default
- `$subscriber` [Subscriber Model](/database/models/subscriber)
**Usage:**
```php
add_filter('fluent_crm/subscriber_info_widgets', function($widgets, $subscriber) {
$widgets[] = [
'title' => 'Membership',
'content' => 'Gold Member
'
];
return $widgets;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php`, `app/Modules/MCP/Tools/ContactTools.php`
---
### `fluent_crm/subscriber_info_widget_{$widgetKey}`
Lazy-load or refresh a **single** sidebar info widget. When the profile sidebar requests
`subscribers/{id}/info-widgets` with a `by_widget` parameter — which the admin UI does when paging a
widget that was registered with `has_pagination` on
[`fluent_crm/subscriber_info_widgets`](#fluent-crm-subscriber-info-widgets) — only this dynamic
filter runs instead of the full widget registry. The dynamic portion, `$widgetKey`, is the array key
the widget was registered under.
Return an array of widget definitions: the controller runs it through `array_values()` and uses the
**first** element as the refreshed widget, falling back to `['content' => 'No content found']` when
the array is empty. In practice producers register the same callback on both hooks — core's Event
Tracking handler registers `addSubscriberInfoWidgets` on `fluent_crm/subscriber_info_widgets` and
`fluent_crm/subscriber_info_widget_event_tracking` alike, and reads the request's `page` parameter to
build the requested page.
**Parameters**
- `$widgets` Array - always empty; return an array whose first element is the widget definition (`title`, `content`, and optionally `has_pagination`, `total`, `per_page`, `current_page`)
- `$subscriber` [Subscriber Model](/database/models/subscriber)
**Usage:**
```php
add_filter('fluent_crm/subscriber_info_widget_my_widget', function($widgets, $subscriber) {
$page = isset($_REQUEST['page']) ? max(1, (int) $_REQUEST['page']) : 1;
$widgets['my_widget'] = [
'title' => __('My Widget', 'my-plugin'),
'content' => 'Page ' . $page . ' content for ' . esc_html($subscriber->email) . '
'
];
return $widgets;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php` (example producer: `app/Hooks/Handlers/EventTrackingHandler.php`)
---
### `fluentcrm_commerce_provider`
Declare the active commerce provider for the CRM. The unfiltered value is an empty string; a
non-empty slug switches on the purchase widget on contact profiles, the `commerce_stat` payload of
the contact API and the commerce columns of the contacts table query. FluentCampaign Pro hooks this
in `fluentcampaign-pro/app/Hooks/filters.php` and returns `woo` or `edd` (via
`Commerce::getCommerceProvider()`) when the matching deep-integration sync is enabled.
::: tip
The returned slug becomes the dynamic part of
[`fluent_crm/contact_purchase_stat_{$provider}`](#fluent-crm-contact-purchase-stat-provider), so a
custom provider must implement that filter too or the purchase widget stays empty.
:::
**Parameters**
- `$provider` String - commerce provider slug. Default `''` (no provider)
**Usage:**
```php
add_filter('fluentcrm_commerce_provider', function($provider) {
return $provider ?: 'my_shop';
});
```
**Source:** `app/Http/Controllers/SubscriberController.php`, `app/Hooks/Handlers/PurchaseHistory.php`, `app/Hooks/Handlers/AdminMenu.php`, `app/Services/ContactsQuery.php` (implemented in `fluentcampaign-pro/app/Hooks/filters.php`)
---
### `fluent_crm/contact_purchase_stat_{$provider}`
Supply the purchase statistics shown on a contact profile (the "commerce stat" blocks of the
purchase widget and the `commerce_stat` field of the contact API response). The dynamic portion,
`$provider`, is whatever [`fluentcrm_commerce_provider`](#fluentcrm-commerce-provider) returned.
FluentCampaign Pro registers the `woo` and `edd` variants
(`fluentcampaign-pro/app/Services/Integrations/WooCommerce/DeepIntegration.php` and
`.../Edd/DeepIntegration.php`).
**Parameters**
- `$stats` Array - stat blocks, empty by default. Pro returns a list of blocks, each with `title` (label), `value` (display HTML), `key` (machine key such as `customer_since`, `last_order_date`, `order_count`, `lifetime_value`, `aov`) and `actual_value` (raw value)
- `$subscriberId` INT - the contact id (not the model)
**Usage:**
```php
add_filter('fluent_crm/contact_purchase_stat_my_shop', function($stats, $subscriberId) {
$stats[] = [
'title' => __('Order Count', 'my-plugin'),
'value' => '12',
'key' => 'order_count',
'actual_value' => 12
];
return $stats;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php`, `app/Hooks/Handlers/PurchaseHistory.php`
---
### `fluentcrm_get_form_submissions_{$provider}`
Supply the rows of the **Forms** tab on a contact profile for one form plugin. The dynamic portion,
`$provider`, is the slug the tab was registered under via
[`fluent_crm/form_submission_providers`](/hooks/filters/webhooks-and-integrations#fluent-crm-form-submission-providers).
Core ships the `fluentform` implementation in `app/Hooks/Handlers/FormSubmissions.php`; the request's
`page` and `per_page` parameters are available for pagination.
::: danger Escape your column values
The returned column values are rendered as raw HTML in the admin UI (Vue `v-html`) with no
client-side sanitization. Escape any contact-authored data with `esc_html()` or `wp_kses_post()`
before returning it. Structural markup (links, badges) is allowed by design.
:::
::: tip
Besides `data` and `total`, the return array may carry a `columns_config` map
(`column_key => ['label' => ..., 'width' => ..., 'quick_action' => true]`) to control the table.
A row that includes a `__id` key and `'action' => 'view'` gets a View button that opens a detail
drawer via [`fluent_crm/dynamic_contact_item_view_{$provider}`](#fluent-crm-dynamic-contact-item-view-provider),
receiving the full row as `$params`.
:::
**Parameters**
- `$data` Array - `['data' => [], 'total' => 0]` by default; `data` is a list of rows (`column key => cell HTML`), `total` the overall submission count
- `$subscriber` [Subscriber Model](/database/models/subscriber)
**Usage:**
```php
add_filter('fluentcrm_get_form_submissions_my_forms', function($data, $subscriber) {
$data['data'][] = [
'__id' => 123,
'id' => '#123',
'title' => esc_html('Contact Form'),
'action' => 'view'
];
$data['total'] = 1;
return $data;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php` (example producer: `app/Hooks/Handlers/FormSubmissions.php`)
---
### `fluent_crm/dynamic_contact_item_view_{$provider}`
Supply the content of the detail drawer that opens from a contact-profile list row — for example the
single-submission view of the Forms tab. The admin UI calls
`subscribers/{id}/dynamic-item-view` with a `provider` and the originating row as `params`, and this
dynamic filter builds what the drawer shows. Core implements the `fluentform` variant
(`app/Hooks/Handlers/FormSubmissions.php`), which reads `__id` from `$params` to load the submission.
The drawer sanitizes `content_html` and `footer_content` client-side (`$sanitize`/DOMPurify) before
rendering — still escape contact-authored data server-side rather than relying on it.
::: warning
A second call site in `app/Http/Controllers/FormsController.php` (`getEntry()`) fires
`fluent_crm/dynamic_contact_item_view_fluentform` with **only two arguments** — the default array and
`['__id' => $id]` — so a callback on this hook must not require the `$subscriber` argument. Core's
own handler registers with `10, 2` for exactly that reason. That call site also passes a smaller
default array (only `content_html`), so do not assume every default key is present.
:::
**Parameters**
- `$dataView` Array - Default `['type' => 'html', 'title' => 'no title', 'content_html' => 'sorry, no content found', 'footer_content' => '']`
- `$params` Array - parameters sent by the UI; for Forms-tab rows this is the row returned by [`fluentcrm_get_form_submissions_{$provider}`](#fluentcrm-get-form-submissions-provider), including `__id`
- `$subscriber` [Subscriber Model](/database/models/subscriber) - not passed on the `FormsController` call site (see warning)
**Usage:**
```php
add_filter('fluent_crm/dynamic_contact_item_view_my_forms', function($dataView, $params) {
$entryId = isset($params['__id']) ? (int) $params['__id'] : 0;
$dataView['title'] = sprintf(__('Entry #%d', 'my-plugin'), $entryId);
$dataView['content_html'] = '' . esc_html(my_plugin_get_entry_text($entryId)) . '
';
return $dataView;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php`, `app/Http/Controllers/FormsController.php`
---
### `fluent_crm/contact_note_fields`
Filter the array of additional fields shown on the Add Note form for contacts.
**Parameters**
- `$fields` Array - Additional field definitions
**Usage:**
```php
add_filter('fluent_crm/contact_note_fields', function($fields) {
$fields[] = [
'key' => 'priority',
'label' => __('Priority', 'fluent-crm'),
'type' => 'select',
'options' => ['low' => 'Low', 'medium' => 'Medium', 'high' => 'High']
];
return $fields;
});
```
**Source:** `app/Services/Helper.php`
---
## Custom Fields & Avatars
### `fluent_crm/global_field_types`
Filter the list of available custom contact field types (text, date, checkbox, etc.).
**Parameters**
- `$fieldTypes` Array - Field type definitions
**Usage:**
```php
add_filter('fluent_crm/global_field_types', function($fieldTypes) {
$fieldTypes['color_picker'] = [
'label' => 'Color Picker',
'type' => 'color_picker'
];
return $fieldTypes;
});
```
**Source:** `app/Models/CustomContactField.php`
---
### `fluent_crm/modify_custom_field_value`
Filter a custom field value when it is read back — once per value from
`Subscriber::custom_fields()`, and once per value when `ContactsQuery` bulk-loads custom fields for a
contact list.
::: warning
Only the value is passed. There is no field key, no field type and no contact, so a callback cannot
tell which field it is transforming or who it belongs to. Treat it as a blanket transform over every
custom field value.
:::
**Parameters**
- `$value` Mixed - the raw stored value
**Usage:**
```php
add_filter('fluent_crm/modify_custom_field_value', function($value) {
// Applies to every custom field value — keep it type-safe
return is_string($value) ? trim($value) : $value;
});
```
**Source:** `app/Models/Subscriber.php`, `app/Services/ContactsQuery.php`
---
### `fluent_crm/default_avatar`
Filter the default avatar URL. This only runs when **Enable Gravatar** is switched off in the
compliance settings — when Gravatar is on, `fluent_crm/get_avatar` runs instead and this one never
fires.
**Parameters**
- `$url` String - Default avatar image URL, `assets/images/avatar.png` from the plugin
- `$email` String - Contact's email address
**Usage:**
```php
add_filter('fluent_crm/default_avatar', function($url, $email) {
return 'https://example.com/custom-avatar.png';
}, 10, 2);
```
**Source:** `app/Functions/helpers.php`
---
### `fluent_crm/get_avatar`
Filter the final avatar URL for a contact. Only runs when **Enable Gravatar** is switched on; the
default value is the Gravatar URL, with a `ui-avatars.com` fallback appended when
**Gravatar fallback** is enabled and a name is available.
**Parameters**
- `$url` String - the Gravatar URL, sized at 128px
- `$email` String - Contact's email address
**Usage:**
```php
add_filter('fluent_crm/get_avatar', function($url, $email) {
// Use a custom avatar service
return 'https://avatars.example.com/' . md5($email);
}, 10, 2);
```
**Source:** `app/Functions/helpers.php`
---
### `fluent_crm/allowed_html_tags`
Filter the array of allowed HTML tags and attributes used in `wp_kses()` for sanitizing content.
**Parameters**
- `$tags` Array - Associative array of tag => attributes
**Usage:**
```php
add_filter('fluent_crm/allowed_html_tags', function($tags) {
$tags['iframe'] = [
'src' => true,
'width' => true,
'height' => true
];
return $tags;
});
```
**Source:** `app/Services/Helper.php`
---
## Smart Codes
### `fluentcrm_contact_smartcodes`
Filter the array of contact smart codes (e.g., `contact.first_name`, `contact.email`) shown in the smart code picker.
**Parameters**
- `$smartCodes` Array - Array of smart code definitions
**Usage:**
```php
add_filter('fluentcrm_contact_smartcodes', function($smartCodes) {
$smartCodes[] = [
'key' => '{{contact.custom_field}}',
'title' => 'Custom Field'
];
return $smartCodes;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluent_crm/general_smartcodes`
Filter the array of general CRM and WordPress smart codes (e.g., `crm.business_name`, `wp.admin_email`).
**Parameters**
- `$smartCodes` Array - Array of general smart code definitions
**Usage:**
```php
add_filter('fluent_crm/general_smartcodes', function($smartCodes) {
$smartCodes[] = [
'key' => '{{crm.site_phone}}',
'title' => 'Site Phone Number'
];
return $smartCodes;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluent_crm/smartcode_groups`
Filter the full grouped array of smart code groups returned by `getGlobalSmartCodes()`.
**Parameters**
- `$smartCodes` Array - Grouped smart code array
**Usage:**
```php
add_filter('fluent_crm/smartcode_groups', function($smartCodes) {
// Add a custom smart code group
$smartCodes['my_group'] = [
'title' => 'My Custom Codes',
'shortcodes' => [
'{{my_group.key1}}' => 'Description 1'
]
];
return $smartCodes;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluent_crm/extended_smart_codes`
Filter the array of additional smart code groups registered by add-ons.
**Parameters**
- `$extendedCodes` Array - Default `[]`
**Usage:**
```php
add_filter('fluent_crm/extended_smart_codes', function($codes) {
$codes[] = [
'title' => 'WooCommerce',
'shortcodes' => [
'{{woo.last_order_total}}' => 'Last Order Total'
]
];
return $codes;
});
```
**Source:** `app/Services/Helper.php`
---
## Bulk Actions & Filters
### `fluent_crm/custom_contact_bulk_actions`
Add custom bulk actions to the contacts table dropdown.
**Parameters**
- `$actions` Array - Default `[]`
**Usage:**
```php
add_filter('fluent_crm/custom_contact_bulk_actions', function($actions) {
$actions[] = [
'label' => __('Sync to External', 'fluent-crm'),
'value' => 'sync_external'
];
return $actions;
});
```
> **Tip:** Handle the action via the `fluent_crm/contact_bulk_action_{$actionName}` dynamic filter.
**Source:** `app/Hooks/Handlers/AdminMenu.php`
---
### `fluent_crm/contact_bulk_action_limit`
Filter the maximum number of contacts to process in a single bulk-action request.
**Parameters**
- `$limit` INT - Default `400`. Values below `1` are clamped to `1`.
- `$request` Request Object - the incoming bulk-action request, so the limit can vary by action
**Usage:**
```php
add_filter('fluent_crm/contact_bulk_action_limit', function($limit, $request) {
return 1000;
}, 10, 2);
```
**Source:** `app/Http/Controllers/SubscriberController.php`
---
### `fluentcrm_advanced_filter_options`
Filter the full array of advanced filter groups for the contact filter UI.
**Parameters**
- `$groups` Array - Filter group definitions
**Usage:**
```php
add_filter('fluentcrm_advanced_filter_options', function($groups) {
// Add a custom filter group
$groups['my_filters'] = [
'label' => 'My Custom Filters',
'options' => [
['label' => 'Has Purchased', 'value' => 'has_purchased']
]
];
return $groups;
});
```
**Source:** `app/Services/Helper.php`
---
### `fluentcrm_ajax_options_{$optionKey}`
Supply the options of a searchable AJAX selector. The advanced contact filters, automation condition
UIs and several funnel settings load their dropdown choices from the `ajax-options` REST endpoint;
a handful of option keys (`woo_categories`, `company_industries`, `company_types`, `users`, …) are
answered directly by `OptionsController::getAjaxOptions()`, and every other key falls through to this
dynamic filter. The dynamic portion, `$optionKey`, is the `option_key` the selector component was
configured with.
Core registers `event_tracking_keys` (`app/Hooks/Handlers/EventTrackingHandler.php`); FluentCampaign
Pro registers around a dozen variants such as `product_selector_lifterlms`
(`fluentcampaign-pro/app/Services/Integrations/LifterLms/DeepIntegration.php`), `woo_coupons`,
`edd_coupons`, `surecart_products` and `product_selector_pmpro`.
**Parameters**
- `$options` Array - always empty; return a list of option arrays, each with an `id` and a `title` (display label). Some producers return extra keys — e.g. the Woo coupon provider uses `name` instead of `title` for its specific selector component (the EDD coupon provider sticks to `id`/`title`) — but `id`/`title` is the shape generic selectors expect
- `$search` String - the term typed into the selector; filter your query with it
- `$includedIds` Array|String - the values already selected in the UI (`values` request parameter); include them in the result so saved selections keep their labels
**Usage:**
```php
add_filter('fluentcrm_ajax_options_my_products', function($options, $search, $includedIds) {
$products = my_plugin_search_products($search, (array) $includedIds);
foreach ($products as $product) {
$options[] = [
'id' => $product->id,
'title' => $product->name
];
}
return $options;
}, 10, 3);
```
**Source:** `app/Http/Controllers/OptionsController.php`
---
## CSV Export & Import Mapping
### `fluentcrm_csv_mimes`
Filter the MIME-type whitelist a CSV upload must match when contacts (or companies) are imported
(`CsvController::upload()` validates the file with a `mimetypes:` rule built from this list).
::: tip
This filter matters on hosts whose PHP `finfo` reports unusual MIME types for CSV files — the
default list already includes oddballs like `application/vnd.ms-excel` and
`application/octet-stream` for that reason. If valid CSVs are rejected on a specific server, add the
type that server reports.
:::
**Parameters**
- `$mimes` Array - Default `['text/csv', 'text/plain', 'application/csv', 'text/comma-separated-values', 'application/excel', 'application/vnd.ms-excel', 'application/vnd.msexcel', 'text/anytext', 'application/octet-stream', 'application/txt']`
**Usage:**
```php
add_filter('fluentcrm_csv_mimes', function($mimes) {
$mimes[] = 'application/x-csv';
return $mimes;
});
```
**Source:** `app/Functions/helpers.php` (`fluentcrmCsvMimes()`, consumed by `app/Http/Controllers/CsvController.php`)
---
### `fluent_crm/subscriber_table_columns`
Filter the contact columns offered as mapping targets when a CSV is uploaded for **import**
(`CsvController::upload()`). It does not affect CSV export.
**Parameters**
- `$columns` Array - a plain list of column slugs a CSV header can be mapped onto
**Usage:**
```php
add_filter('fluent_crm/subscriber_table_columns', function($columns) {
$columns[] = 'custom_field';
return $columns;
});
```
**Source:** `app/Http/Controllers/CsvController.php`
---
### `fluentcrm_user_map_data`
Filter the [Subscriber](/database/models/subscriber) data array when mapping a WordPress user to a CRM contact during sync.
**Parameters**
- `$subscriber` Array - Subscriber data being mapped
- `$user` \WP_User - WordPress user object
**Usage:**
```php
add_filter('fluentcrm_user_map_data', function($subscriber, $user) {
$subscriber['company_id'] = get_user_meta($user->ID, 'company_id', true);
return $subscriber;
}, 10, 2);
```
**Source:** `app/Services/Helper.php`
---
### `fluentcrm_update_wp_user_email_on_change`
Control whether a CRM email change is pushed to the linked WordPress user. It is consulted in two
places:
- `Subscriber` model — when a saved contact's email no longer matches its linked WP user, returning `true` writes the new address onto the WP user via `wp_update_user()`.
- `SubscriberController` — when a contact is edited to an email that matches no WP user, returning `true` keeps the contact linked to its existing WP user instead of unlinking it.
**Parameters**
- `$update` Boolean - Default `false`
**Usage:**
```php
add_filter('fluentcrm_update_wp_user_email_on_change', function($update) {
return true; // Sync email changes to WP user
});
```
**Source:** `app/Models/Subscriber.php`, `app/Http/Controllers/SubscriberController.php`
---
### `fluent_crm/get_import_driver_{$driver}`
First phase of a custom contact-import driver: build the configuration screen. Any import driver
that is not the built-in `users` (or CSV) handler is dispatched through this dynamic filter by
`ImporterController::getDriver()`. The dynamic portion, `$driver`, is the key the driver was
registered under on
[`fluent_crm/import_providers`](/hooks/filters/webhooks-and-integrations#fluent-crm-import-providers) —
without that registration the driver never shows up in the import UI.
The controller calls the same filter twice in the wizard:
1. **Screen definition** — return `['config' => [...defaults...], 'fields' => [...field definitions...]]` describing the driver's settings form.
2. **Summary/preview** — when the request carries `summary`, return `['import_info' => ['subscribers' => [...], 'total' => N, 'has_list_config' => true, 'has_status_config' => true, 'has_update_config' => true, 'has_silent_config' => true]]` so the wizard can show a preview and the standard list/status/update options.
Returning `false` (the default) or a `WP_Error` aborts with "Sorry no driver found for this import"
(or the `WP_Error` message). The actual row processing happens in the second hook,
[`fluent_crm/post_import_driver_{$driver}`](#fluent-crm-post-import-driver-driver).
::: tip
FluentCampaign Pro's `fluentcampaign-pro/app/Services/Integrations/BaseImporter.php` wires both
phases for its LMS/membership importers; `PMProImporter` in the same directory is a complete
reference implementation of the two-phase contract.
:::
**Parameters**
- `$response` Boolean|Array|\WP_Error - Default `false`
- `$request` Request Object - the current wizard request; read `config` and `summary` from it
**Usage:**
```php
add_filter('fluent_crm/get_import_driver_my_source', function($response, $request) {
if ($request->get('summary')) {
return [
'import_info' => [
'subscribers' => my_source_preview_rows(5),
'total' => my_source_total(),
'has_list_config' => true,
'has_status_config' => true,
'has_update_config' => true,
'has_silent_config' => true
]
];
}
return [
'config' => ['import_type' => 'all'],
'fields' => [
'import_type' => [
'label' => __('Import by', 'my-plugin'),
'type' => 'input-radio',
'options' => [
['id' => 'all', 'label' => __('All Customers', 'my-plugin')]
]
]
]
];
}, 10, 2);
```
**Source:** `app/Http/Controllers/ImporterController.php`
---
### `fluent_crm/post_import_driver_{$driver}`
Second phase of a custom contact-import driver: process one page of the actual import.
`ImporterController::importData()` fires this repeatedly — once per batch — with the config collected
by [`fluent_crm/get_import_driver_{$driver}`](#fluent-crm-get-import-driver-driver) and an
incrementing page number, until the returned progress data reports no more pages. Import your batch
(Pro's importers use `Subscriber::import()` with a limit of 100 users per page) and return the
progress array the wizard polls on.
Returning `false` (the default) or a `WP_Error` aborts with an error message, so an unrecognized
config is safe to reject.
**Parameters**
- `$response` Boolean|Array|\WP_Error - Default `false`. On success return progress data: `['page_total' => ..., 'record_total' => ..., 'has_more' => bool, 'current_page' => $page, 'next_page' => $page + 1]`
- `$config` Array - the driver config the user filled in on the screen from phase one
- `$page` INT - the 1-based batch number for this request
**Usage:**
```php
add_filter('fluent_crm/post_import_driver_my_source', function($response, $config, $page) {
$perPage = 100;
$total = my_source_total();
my_source_import_batch($config, $page, $perPage);
return [
'page_total' => ceil($total / $perPage),
'record_total' => $total,
'has_more' => $total > ($page * $perPage),
'current_page' => $page,
'next_page' => $page + 1
];
}, 10, 3);
```
**Source:** `app/Http/Controllers/ImporterController.php`