Skip to Content

Search Usage

A listing page has two search mechanisms. They look similar but they are not the same filter on the backend.

Main searchPer-field search
WhereThe search bar on top of the listingInside the Filters panel, on a text field
Query param?search=foo bar?title_partial=foo, ?title=foo
Backend filterMySearchFilter / SearchTermsFilterApiPlatform\Doctrine\Orm\Filter\SearchFilter
ScopeAll the configured fields at once, with OR clausesOne field, one strategy at a time

One term, or many terms, searched in every field the entity has configured in its MySearchFilter. It also covers the nested relations, the translations and the shortcode IDs (ITY5 becomes id = 5).

GET /{resourceName}?search=foo bar

The full behaviour (tokenizing, searchable fields, translation support, shortcode IDs and the case-insensitive matching) is documented on the backend side: MySearchFilter.

This one is not generated from the metadata. The CRUD generator skips MySearchFilter when it reads apiFilters, so the search bar is added by the frontend on every listing, and it is the filter declaration on the backend that decides which fields it really covers.


Per-field search methods

When a text filter has more than one strategy, we display a selector next to the input and the user picks how the term should be matched:

A Title filter input with a Contains selector open, listing Contains, Starts with, Ends with and Exact

Every label is one API-Platform strategy, and every strategy sends a different query key:

Selector labelStrategyQuery key
Containspartial{field}_partial
Starts withstart{field}_start_with
Ends withend{field}_end_with
Exactexact{field}

Only one strategy is active at a time. When the selector is changed, the keys of the other strategies are cleared and the typed value is moved to the new key, so the listing never sends two matches for the same field. If there is no value yet, the selector goes back to the first strategy of the list.


How it is generated

The selector is not written by hand per entity. It comes from the metadata, through the generator:

  1. The generator reads apiFilters and, for every property of the SearchFilter entry, it takes the declared strategy.
  2. The strategies of a field are ordered by priority (partial, start, end, exact) and stored as filterFields.searchMethods in data.json. More about it in Metadata API Usage.
  3. changes.json keeps the same shape ("searchMethods": { "name": ["partial", "start", "exact"] }), so the list can be trimmed by hand for that entity. See the changes.json Reference.
  4. The generated index.tsx prints searchMethods on the filter field, and ListingFilters displays the selector when there is more than one entry in it.

A few cases never get a selector:

  • Relation filters. When the properties of the filter is an array instead of a {property: strategy} object, no strategy is stored for it.
  • Translatable fields. A property declared as translations.title is kept as a translatable field and it is handled by the TranslationSearchFilter, not as a normal search field.
  • text fields. They are removed from the filters list, because a textarea is not really a filter input.

Last updated on