Ibexa DXP: rendering a content type with views and Twig
Configure a content view, write the Twig template and add an article list with a custom controller and the SearchService.
In Ibexa DXP, editors create content in the back office, and it's up to us developers to decide how it is displayed. The core mechanism for that is content views. Let's take a concrete example: an article content type with title, intro, image and body fields.
1. Declare the view
In config/packages/ibexa.yaml, a matcher links a template to a content type:
ibexa:
system:
site_group:
content_view:
full:
article:
template: '@ibexadesign/full/article.html.twig'
match:
Identifier\ContentType: article
full is the view type used for a full page. You can define other view types, such as line for an item in a list, following the same principle.
2. Write the template
Ibexa provides Twig functions to render fields properly:
{% extends '@ibexadesign/pagelayout.html.twig' %}
{% block content %}
<article>
<h1>{{ ibexa_content_name(content) }}</h1>
{% if not ibexa_field_is_empty(content, 'image') %}
{{ ibexa_render_field(content, 'image') }}
{% endif %}
{{ ibexa_render_field(content, 'intro') }}
{{ ibexa_render_field(content, 'body') }}
</article>
{% endblock %}
ibexa_render_field uses the right renderer for each field type: rich text, images with their variations, and so on. Prefer it over printing raw values.
3. Add a list with a controller
For a "Blog" page listing its child articles, plug a controller into the parent folder's view:
folder_blog:
template: '@ibexadesign/full/blog.html.twig'
controller: App\Controller\BlogController::list
match:
Identifier\ContentType: folder
Identifier\Section: blog
The controller receives the view, queries the search engine and adds the results to the template parameters:
namespace App\Controller;
use Ibexa\Contracts\Core\Repository\SearchService;
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;
use Ibexa\Core\MVC\Symfony\View\ContentView;
final class BlogController
{
public function __construct(private readonly SearchService $searchService) {}
public function list(ContentView $view): ContentView
{
$query = new Query([
'filter' => new Criterion\LogicalAnd([
new Criterion\ParentLocationId($view->getLocation()->id),
new Criterion\ContentTypeIdentifier('article'),
new Criterion\Visibility(Criterion\Visibility::VISIBLE),
]),
'sortClauses' => [new SortClause\DatePublished(Query::SORT_DESC)],
'limit' => 10,
]);
$articles = array_map(
static fn ($hit) => $hit->valueObject,
$this->searchService->findContent($query)->searchHits
);
$view->addParameters(['articles' => $articles]);
return $view;
}
}
In the template, each article can then be rendered with its line view:
{% for article in articles %}
{{ render(controller('ibexa_content::viewAction', {
contentId: article.id,
viewType: 'line'
})) }}
{% endfor %}
Best practices
- Use
filterrather thanquerywhen you're not doing full-text search: it's faster and skips relevance scoring. - Plan for pagination as soon as a list can grow, for example with Pagerfanta and the adapter Ibexa provides.
- Let HTTP caching do its job: content views are tagged automatically and the cache is purged on publish.
- For reusable lists, Query Types let you declare a query once and configure it directly in YAML.
With these three building blocks — view, template, controller — you already cover most display needs of an Ibexa site.