ibexa/search
Ibexa Search provides the search engine integration layer for Ibexa DXP and Ibexa Open Source, powering content queries and indexing. Commercial use requires an Ibexa DXP subscription; GPLv2 otherwise.
ibexa/search is not a standalone search library you reach for on a greenfield project — it is the search integration layer that ships with Ibexa DXP / Ibexa Open Source. In practice you consume it through the platform's SearchService and the search UI it wires up, not by calling classes from this package directly.
Minimal setup
composer require ibexa/oss (or ibexa/dxp), which pulls ibexa/search in lockstep with the rest of the release train. Confirm with composer show ibexa/search.bin/console ibexa:reindex.Where to look first
vendor/ibexa/search/src and the bundle's Resources/config/*.yaml — that is where the service tags and extension points actually live.First use case: run a query
Start with the public API rather than package internals:
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion;
use Ibexa\Contracts\Core\Repository\Values\Content\Query\SortClause;
$query = new Query();
$query->query = new Criterion\FullText('running shoes');
$query->filter = new Criterion\ContentTypeIdentifier(['product']);
$query->sortClauses = [new SortClause\ContentName(Query::SORT_ASC)];
$query->limit = 20;
$query->offset = 0;
$result = $this->searchService->findContent($query);
Once that works, move to the higher-level features this package owns: aggregations (facets), user-selectable sorting, the autocomplete/suggestion endpoint, and spellcheck.
Query composition
Build queries from Criterion leaves plus LogicalAnd / LogicalOr / LogicalNot composites. Keep the full-text criterion separate from structural filters (ContentTypeIdentifier, SectionId, ParentLocationId, Visibility) so the backend can cache and rank efficiently. Set $query->performCount only when you actually need a total — it is the expensive part on large indexes.
Facets / aggregations Facets are declared on the query as aggregations and read back from the search result; the UI layer renders them as term/range filters. The workflow that holds up:
Query.FacetResult entries off the result and map them to your filter form.Criterion entries on the next query.Sorting
Ibexa's UI sorting (added in 5.0) only exposes sorts you allowlist. Implement it as a small resolver: take a request parameter, validate against an enum of allowed sort clauses, and default to relevance. Always append a stable tie-breaker (SortClause\ContentId) so pagination does not shuffle equal-ranked documents between pages. Date sorts should use DatePublished / DateModified explicitly rather than a generic Field sort on a date attribute.
Autocomplete / suggestions The suggestion endpoint returns autocomplete candidates plus the total result count (the count was added for the suggestions endpoint in the 4.6/5.0 line). Treat it as a separate, cheap query path:
Result mapping
If you need to reshape hits into view models (badges, highlighted snippets, pager rows), the PagerSearchDataMapper contract introduced in 5.0 is the intended seam. Implementing it keeps presentation logic out of templates and out of your controllers.
Extending the backend translation When you need a criterion the platform does not support, add a criterion visitor and register it as a tagged service (check the bundle YAML for the exact tag name in your version). The same pattern applies to sort clause visitors and aggregation visitors. This is the only supported way to teach the search backend about a new query shape — do not hand-build backend query strings from application code.
Keep queries behind a service
Inject SearchService into a dedicated ProductSearch / ArticleSearch class, not into controllers. You get one place to add caching, logging, and permission reasoning, and one place to swap in a fake for tests.
NOASSERTION because the code is dual-licensed: Ibexa Business Use License (requires a DXP subscription) and GPLv2. Decide which applies to you before forking or vendoring; GPLv2 is copyleft and the BUL requires a paid subscription. Get this signed off early, not at release time.class_alias BC layer, renamed domains, required PHP 8.3/Symfony 6→7, and introduced PagerSearchDataMapper. If you are on 4.x, stay on the 4.6 LTS branch; do not cherry-pick 5.x behavior.bin/console ibexa:reindex) and clear cache after content type or field configuration changes before debugging PHP.FullText is broader than you think. A fix in the 5.0 line addressed the criterion matching all text fields. If precision matters, prefer explicit field criteria and treat FullText as a relevance-scored catch-all.Resources/config for tags — criterion visitors, sort visitors, aggregation visitors, and mappers are usually already wired and just need your class registered.How can I help you explore Laravel packages today?