Skip to content

Autocomplete (APIv4 EntityReference) Fields

Overview

An Autocomplete is a dropdown field for selecting from a list of existing records. The query and results are handled by Search Kit. The widget is a Select2.

Note

As of this writing in 2023, CiviCRM is gradually migrating from the older v3-based EntityRef fields to v4-based Autocompletes. Afforms, custom fields of type Entity Reference, and the Search Kit UI already use this new widget, whereas other Quickform and Angular fields use the old one.

Adding an Autocomplete to a Form

  • PHP (QuickForm): Use the CRM_Core_Form::addAutocomplete() method.
  • Javascript (jQuery): Start with an <input> element and call $('#element-selector).crmAutocomplete().
  • AngularJS: Use the crmAutocomplete directive on an <input> element.

The APIv4 autocomplete action

To populate its results, the form widget will call (e.g. for a Contact autocomplete):

CRM.api4('Contact', 'autocomplete', {
    formName: 'x',
    fieldName: 'y',
    input: 'z',
    page: 1,
    filters: {contact_type: 'Individual'}
});

This autocomplete API action in turn calls Search Kit's SearchDisplay::run API to run the search and output the results. Several events are fired which allow developers to modify the behavior, and they receive contextual information like entityName, formName and fieldName to help listeners decide which events to respond to:

Modifying the Filters

By default, all filters passed to the autocomplete api will be silently discarded if those fields are not already part of the displayed output. This is by design to keep the api secure. In the above example, passing the filter {contact_type: 'Individual'} will work, because contact_type is already shown by default in autocomplete results. However most other fields would not work as filters (e.g. external_identifier, api_key, gender_id are not displayed as part of a contact's autocomplete results so would not be accepted as filters).

If you want non-displaying field to be accepted as fields from the client, or you wish to hard-code other filters in a particular autocomplete, add a callback for the civi.api.prepare event. E.g.:

  // Assuming your class implements HookInterface, AutoService, and your extension has the @scan-classes mixin:
  public function on_civi_api_prepare(\Civi\API\Event\PrepareEvent $event): void {
    $apiRequest = $event->getApiRequest();
    if (
      // APIv3 requests are not an object so check that first
      is_object($apiRequest) &&
      // We're only interested in Autocomplete actions 
      is_a($apiRequest, 'Civi\Api4\Generic\AutocompleteAction') &&
      // Conventions for formName are "afform:afNameOfMyForm" for afforms and "qf:CRM_CLASS_OF_MY_FORM" for quickforms
      $apiRequest->getFormName() === 'whatever-you-are-expecting-for-formName' &&
      // Convention for fieldName is usually "entity:field" so e.g. "Participant:event_id"
      $apiRequest->getFieldName() === 'whatever-you-are-expecting-for-fieldName' &&
      // Or for less specificity you could respond to all autocompletes for a particular entity
      $apiRequest->getEntityName() === 'Event'
    ) {
      // Filters from the client request; will probably be discarded unless we intervene
      $clientFilters = $apiRequest->getFilters();
      // Allow filtering by campaign
      if (isset($clientFilters['campaign_id'])) {
        // The `AutocompleteAction::addFilter` method both sets the value and "whitelists" the filter to ensure it gets applied.
        $apiRequest->addFilter('campaign_id', $clientFilters['campaign_id']);
      }
    }
  }

Modifying the Display

If you wish to alter the SELECT, ORDER BY, LIMIT, or to change the formatting of the dropdown results, use this technique to alter the display.

Most entities rely on the default autocomplete display generated by DefaultDisplaySubscriber::autocompleteDefault which returns label, id, icon, color, etc gathered from the entity's metadata. Other entities which do not have a label or title require a function to define a default display. For example, the ParticipantAutocompleteProvider combines the name of the contact with the name of the event for a meaningful label.

Tip

At least one column is requred. The first is the primary text of the search results, and (if the column uses rewrite) all fields displayed by this column will be the fields searched on (combined with OR).

If you need to provide a default display for an entity, or you wish to override the display of an autocomplete, add a callback for the 'civi.search.defaultDisplay' event. E.g.:

  // Assuming your class implements HookInterface, AutoService, and your extension has the @scan-classes mixin:
  public function on_civi_search_defaultDisplay(\Civi\Core\Event\GenericHookEvent $event): void {
    if ($event->display['type'] === 'autocomplete' && $event->savedSearch['api_entity'] === 'Participant') {
      $event->display['settings'] = [
        'sort' => [...],  
        'columns' => [...],
        'extra' => [...],
      ];
    }
  }

Display Fields will Auto-Select

When adding a field to the display (even as a token in e.g. a rewrite) it will be auto-added to the SELECT clause for you; no need to modify the query just to add a field.

Modifying the Query

If you wish to add WHERE, HAVING, or JOIN clauses to an autocomplete, use this technique (note that other query parameters are controlled by the Display, see above).

The query parameters for an Autocomplete are modeled as a Search Kit SavedSearch -- although in most cases the search isn't acually saved anywhere; by default it starts with an empty array as the API parameters. SearchKit doesn't actually need any api params other than [version => 4] to run a display, since the display will add all of its fields to the SELECT and control the ORDER BY based on its 'sort' param.

If you need to change the query in other ways, e.g. to JOIN to another entity, add a callback for the 'civi.search.autocompleteDefault' event. This event has access to formName and fieldName (added in CiviCRM 5.64) in case you want to do it for a specific field, or it can be per-entity like in the CaseAutocompleteProvider which adds a join on CaseContact so the name of the client can be displayed (and searched on) with the case subject.

Here is an example from the Per-contact Note Access extension to list only contacts that have a CMS account:

  // Assuming your class implements HookInterface, AutoService, and your extension has the @scan-classes mixin:
  public function on_civi_search_autocompleteDefault(GenericHookEvent $event): void {
    if (!is_array($event->savedSearch) || $event->savedSearch['api_entity'] !== 'Contact') {
      return;
    }

    if ($event->formName === 'qf:CRM_Note_Form_Note' && $event->fieldName === 'noteaccess_contact_id') {
      $event->savedSearch['api_params'] = [
        'version' => 4,
        'select' => [
          'id',
          'display_name',
          'contact_type',
        ],
        'orderBy' => [],
        'where' => [],
        'groupBy' => [],
        'join' => [
          [
            'UFMatch AS uf_match',
            'INNER',
            ['uf_match.contact_id', '=', 'id'],
          ],
        ],
        'having' => [],
      ];
    }
  }