Skip to content

Add OnManagerSearch event to allow extending quick search (uberbar) #16912

@biz87

Description

@biz87

Feature request

Summary

The manager's quick search (uberbar) currently searches a fixed set of object types (Resources, Elements, Users) with no extension mechanism. Extras that manage their own data types (e.g., Commerce products, MIGX items, custom tables) cannot integrate with the search bar. Adding a system event would allow plugins to register custom search providers.

Current behavior

The Search/Search processor (core/src/Revolution/Processors/Search/Search.php) has a well-designed generic method searchElements() that accepts a class, type identifier, and field names:

protected function searchElements($class, $type, $nameField, $descriptionField, $contentField)

However, this method is:

  • Not extensible — the list of searched types is hardcoded in process()
  • Limited in output — the _action URL is hardcoded as 'element/' . $type . '/update&id='
  • No event — there is no system event like OnManagerSearch that plugins could hook into

The frontend (modx.searchbar.js) already supports custom result types — it checks for label and icon fields before falling back to built-in mappings:

getLabel: function(values) {
    if (values.label) { return values.label; }
    return _('search_resulttype_' + values.type);
}
getClass: function(values) {
    if (values.icon) { return values.icon; }
    // ... switch/case fallback
}

So the frontend is ready for extension; the backend is not.

Suggested solution

Add an OnManagerSearch system event that fires during quick search processing. Plugins respond by providing search provider configurations — structured arrays describing what to query. The processor builds and executes queries using these configs, following the same pattern as the existing searchElements().

Provider configuration structure:

// Plugin attached to OnManagerSearch
$providers = &$modx->event->params['providers'];
$providers[] = [
    'class'            => 'MyExtra\Model\Product',      // xPDO model class (required)
    'type'             => 'products',                    // Result group identifier (required)
    'label'            => 'Products',                    // Display label for group header
    'icon'             => 'shopping-cart',                // FontAwesome icon name
    'permission'       => 'myextra_view',                // Permission to check (optional)
    'nameField'        => 'name',                        // Primary search field (required)
    'descriptionField' => 'sku',                         // Secondary search/display field
    'contentField'     => 'description',                 // Content field (respects quick_search_in_content)
    'action'           => 'myextra/product/update&id=',  // URL pattern, ID is appended (required)
    'joins'            => [                              // Optional xPDO joins
        [
            'class' => 'MyExtra\Model\Category',
            'alias' => 'Category',
            'on'    => 'Product.category_id = Category.id',
            'type'  => 'left',
        ],
    ],
    'searchFields'     => [                              // Additional fields to search (from joins)
        'Category.name',
    ],
];

How the processor would handle it:

// In process(), after built-in searches:
$providers = [];
$this->modx->invokeEvent('OnManagerSearch', [
    'query'      => $this->query,
    'providers'  => &$providers,
    'maxResults' => $this->getMaxResults(),
]);

foreach ($providers as $provider) {
    if (!empty($provider['permission']) && !$this->modx->hasPermission($provider['permission'])) {
        continue;
    }
    $this->searchProvider($provider);
}

The searchProvider() method would follow the same query-building logic as searchElements() — LIKE conditions on specified fields, sorting by exact match, respecting quick_search_result_max limit — but with support for joins, custom action URLs, and custom icons/labels.

Why this approach

  1. Reuses existing patternssearchElements() already does exactly this for built-in types; the new method generalizes it
  2. Declarative, not imperative — plugins describe what to search, the processor handles how. This ensures consistent query building, limit enforcement, and result formatting
  3. Frontend already supports it — no JS changes needed beyond an optional default icon for unknown types
  4. Safe — the processor controls query execution, so plugins can't inject arbitrary SQL or bypass limits
  5. Consistent UX — results from extras look and behave identically to built-in results

Additional considerations

  • The searchElements() _action is currently hardcoded as 'element/' . $type . '/update&id='. This should be parameterized regardless of the event, as it's a limitation even for potential future core search types
  • Result validation should filter out malformed provider configs (missing required fields)
  • Event groupname: System (same as OnManagerPageInit and similar manager events)
  • Event service: 2 (plugin event)

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureRequest about implementing a brand new function or possibility.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions