Customer Extra Widget
This feature allows the addition of extra widgets for customers on the view ticket page.
Hook Name:
fluent_support/customer_extra_widgets
Description:
This filter, fluent_support/customer_extra_widgets, serves as a gateway for developers to enhance the functionality of the view ticket page within Fluent Support. When developers utilize this filter, they can integrate custom widgets seamlessly into the view ticket interface. These widgets can contain diverse HTML content tailored to specific needs, such as presenting supplementary information, offering additional features, or facilitating user interactions.
By leveraging this filter, developers gain the flexibility to extend the capabilities of Fluent Support's view ticket page according to their project requirements. This extensibility empowers them to enrich the user experience by integrating bespoke elements that cater to unique use cases or business scenarios. Additionally, developers can leverage this filter to maintain consistency with the overall design and functionality of Fluent Support while introducing customizations tailored to their applications or projects.
Parameters
$widgets(array): An associative array containing information about the widgets to be displayed. Each element of the array represents a single widget and includes a unique key and an array of widget data.customer(object): You can use the customer data to check user permissions and other related information.
Example:
add_filter('fluent_support/customer_extra_widgets', function ($widgets, $customer) {
ob_start();
?>
<ul>
<li title="Custom Widget List 1" class="fs_widget_li">
<code>Widget Title: Custom Widget List 1</code><br>
<code>Description: This is the first custom widget</code>
</li>
<li title="Custom Widget List 2" class="fs_widget_li">
<code>Widget Title: Custom Widget List 2</code><br>
<code>Description: This is the second custom widget</code>
</li>
</ul>
<?php
$content = ob_get_clean();
$widgets['custom_widget_1'] = [
'header' => __('Custom Widget 1', 'your-text-domain'),
'body_html' => $content
];
return $widgets;
}, 10, 2);Output:

Notes:
- Ensure that the HTML content generated for custom widgets is properly formatted and accessible.
- Use unique keys for each custom widget to avoid conflicts.
Widgets for AI Agents (MCP)
AI agents connected through the Fluent Support MCP server read the same widgets when they call get-ticket. They don't need HTML. Since Fluent Support 2.4.5 the filter receives a third argument, $context, that says what the caller wants, so a widget can return plain data to agents and skip work nobody asked for.
The $context argument
$context(array|null):['format' => 'html'|'mcp', 'keys' => string[]|null]format:'html'for the agent UI (the ticket and customer pages),'mcp'for AI agents.keys: the widget keys the caller asked for, ornullfor all. An agent passes these withget-ticket'swith_integrationsparameter, e.g.["custom_widget_1"].
Register the filter with 3 accepted arguments to receive it. Two helpers read it:
FluentSupport\App\Services\ProfileInfoService::wantsWidget($context, $key):truewhen the caller wants this widget. Call it before running any query.FluentSupport\App\Services\ProfileInfoService::isMcpWidgetRequest($context):truewhen the caller wantsmcpdata instead ofbody_html.
Returning data with the mcp key
For an MCP request, return an mcp array instead of body_html. The agent receives it as integrations.<widget_key>.data, with fields it can read directly instead of prose.
use FluentSupport\App\Services\ProfileInfoService;
add_filter('fluent_support/customer_extra_widgets', function ($widgets, $customer, $context = null) {
// Skip the queries when an AI agent asked for other widgets only.
if ($context && !ProfileInfoService::wantsWidget($context, 'my_account')) {
return $widgets;
}
$account = my_plugin_get_account($customer->email);
if (!$account) {
return $widgets;
}
if ($context && ProfileInfoService::isMcpWidgetRequest($context)) {
$widgets['my_account'] = [
'title' => __('Account', 'your-text-domain'),
'mcp' => [
'plan' => $account->plan, // 'paid'
'status' => $account->status, // raw slug, e.g. 'active'
'renews_at' => gmdate('c', $account->renews_at),
'teams' => array_map(function ($team) {
return [
'id' => (int) $team->id,
'name' => $team->name,
'bounce_rate' => (float) $team->bounce_rate,
'url' => admin_url('admin.php?page=my-plugin&team=' . $team->id),
];
}, $account->teams),
],
];
return $widgets;
}
ob_start();
// ...render the HTML for the agent UI
$widgets['my_account'] = [
'header' => __('Account', 'your-text-domain'),
'body_html' => ob_get_clean(),
];
return $widgets;
}, 10, 3);Writing good mcp data
- Put a few summary fields first (
plan,status,total_orders), then lists (orders,licenses,teams). - Use raw values: status slugs, ISO 8601 dates (
nullfor "never"), money as a number plus an ISO currency code ('currency' => 'USD'), never a symbol or HTML entity. - Add a
urlto items the agent may need to open. - Build it from the data the widget already loads. It runs on every
get-ticketcall.
Sanitizing limits
mcp data is treated as untrusted integration output. Fluent Support keeps:
- Scalars only: strings, integers, floats, booleans and
null. Objects are dropped. - Strings with tags and control characters stripped, cut to 500 characters.
- At most 50 items per array.
- At most 5 levels of nesting, counting the
mcparray itself.mcp => orders[] => {items[] => {name, qty}}fits; anything deeper is dropped. - Array keys passed through
sanitize_key().
Without the mcp key
Widgets that only return body_html keep working. For MCP, Fluent Support converts the HTML to Markdown, so lists, tables and links keep their shape, and sends it as integrations.<widget_key>.data.summary. A summary string set on the widget is used instead when present. Widgets that ignore keys are still filtered out afterwards, but their queries have already run.
Backward compatibility
- Older Fluent Support versions call the filter with two arguments and don't have the helpers. Give
$contextanulldefault and guard every helper call with$context &&, as in the example. With anullcontext the callback takes the HTML path, exactly as before. - Older callbacks registered with 2 accepted arguments never see
$contextand keep returning HTML.
Customer Summary in Ticket Lists (MCP)
The MCP list-tickets tool can show a one-line customer_summary on each row, so an agent can triage a page of tickets without opening each one. Integrations provide it with the fluent_support/mcp_customer_list_meta filter.
add_filter('fluent_support/mcp_customer_list_meta', function ($lines, $customerIds, $customers, $context) {
// One query for the whole page, not one per customer.
$accounts = my_plugin_get_accounts_by_emails(wp_list_pluck($customers, 'email'));
foreach ($customers as $customerId => $customer) {
if (isset($accounts[$customer->email]) && !isset($lines[$customerId])) {
$account = $accounts[$customer->email];
$lines[$customerId] = sprintf('%s plan · %d sent/30d · domain %s', $account->plan, $account->sent_30d, $account->domain_status);
}
}
return $lines;
}, 10, 4);- It fires once per list page with every customer on it. Use one query for all of them, and no remote calls; cache anything expensive.
- Merge into
$linesand keep keys that are already set. - Values are plain text: tags and control characters are stripped and lines are cut to 120 characters. Non-scalar values and customer IDs not on the page are dropped.
- It only runs for users with the "Access Private Data (Customers, Agents)" permission, since the line can carry billing or CRM details.
Widgets from a Remote Portal main site
With Remote Portal, the same filter also runs on the main site through the Fluent Support Client plugin, and those widgets reach the support site keyed <key>_remote. See Customer widgets from the main site.
