ibexa/fieldtype-query
Ibexa Query Field Type lets content managers map an executable Repository Query directly to a content field, making dynamic, query-driven content easy to configure within Ibexa DXP. Part of the Ibexa DXP package suite.
Install & confirm it is active. The Field Type ships as part of Ibexa DXP and is normally pulled in by the ibexa/experience / ibexa/dxp meta-packages. If you are on a slim install:
composer require ibexa/fieldtype-query:^5.0 # match your DXP major (^4.6 for v4.6 LTS)
Pin the package to the same major as your DXP install — the package is versioned in lockstep with Ibexa (4.6.x, 5.0.x), and 5.x requires PHP 8.3 + Symfony 7. Confirm the Field Type appears when you add a field to a content type in Admin → Content Types → [type] → Add field (search for Query). If it does not, the bundle is not registered — check config/bundles.php and clear the cache.
Read the source first — it is small. Everything you need is in vendor/ibexa/fieldtype-query/src/:
FieldType/ — the Field Type class, its Value object and the field-definition settings/form mappers (this is where the query configuration schema lives).Resources/config/ — service definitions, Field Type registration, the admin form (query builder) wiring.Resources/views/ — the editing template and any GraphQL/REST wiring.Twenty minutes here answers every question this package raises faster than the docs do.
First use case — a "latest/related" block. Create a Folder (or Landing Page) content type and add a Query field, e.g. related_query. In the field definition configure it to search ContentTypeIdentifier = article, sort by DatePublished desc, limit 5. Then in a controller:
$value = $content->getFieldValue('related_query');
$query = $value->getQuery(); // Ibexa\Contracts\Core\Repository\Values\Content\Query
$results = $this->contentService->findContent($query);
// or ->findContentInfo($query) if you only need metadata for a listing
Pass $results to the template and render names/URLs with ibexa_content_name / ibexa_path. The Field Type stores and resolves the query definition — your code executes it, which is exactly what makes it composable.
Wrap execution in one service. Do not scatter findContent() calls through controllers/templates. A small resolver gives you one place for caching, permission filtering and fallbacks:
final class QueryFieldResolver
{
public function __construct(private ContentService $contentService) {}
public function resolve(Content $content, string $fieldIdentifier, int $limit = 10, int $offset = 0): ContentList
{
$query = clone $content->getFieldValue($fieldIdentifier)->getQuery();
$query->limit = $limit;
$query->offset = $offset;
return $this->contentService->findContent($query);
}
}
Always clone before paginating. The Query object exposes mutable $limit, $offset, $sortClauses, $filter. Mutating the one returned by the value object leaks state if the same content/field is rendered twice on a page (header + footer + a block). Clone first, then override limit/offset from the request (?page=N).
Hand-picked beats automatic — combine field types. The classic editorial pattern is a relationlist field ("manual picks") that, when empty, falls back to the Query field ("automatic"). Editors get an override without needing a developer:
{% set items = manual_picks is not empty ? manual_picks : query_results %}
Use it as a per-content-type "config object". Because the query belongs to the field definition / content structure, it is ideal for reusable listings driven by content: "products in category X", "news tagged Y". Duplicating the same query across many content types is the smell that you want a shared query content item or a custom Field Type instead.
GraphQL / headless. The field is exposed through Ibexa's GraphQL schema (the package ships the resolver wiring — the deprecated @=resolver syntax was replaced in v5.0.7 / v4.6.29, so check your custom config/graphql/types overrides if you extend the schema). Note that a headless consumer receives the query definition, not the results; the actual content still has to be fetched by the client or by your own endpoint.
Content migrations. The query configuration ends up as a nested array in the Field Definition settings (filter/criteria, sort clauses, limit, offset). Define it in ibexa/migrations alongside the field definition so staging and production stay identical — never configure queries only through the admin UI on production.
ContentService::findContent() will return content the current user may not be allowed to read in some configurations — filter with canRead/canUser or hide at render time. Do not assume the Field Type did it for you.FullText, facets, custom fields). Test the same content type against the engine you actually run in production, and remember to reindex after changing index-time fields.limit explicitly in the field configuration and clamp it in code when paginating.var_dump($fieldDefinition->getFieldSettings()) to learn the shape.IBX-9947) — content types created on older versions referencing the previous identifier need updating/migrating when you upgrade. If a field suddenly has no value after an upgrade, check the identifier first.$query->filter is a deeply nested Criterion tree — dump it with json_encode rather than print_r. To see what actually ran, decorate the repository search service or enable the Ibexa\FieldTypeQuery log channel; the compiled query (filter + sort + limit) is the fastest way to explain "why is this empty?".How can I help you explore Laravel packages today?