Registering a Custom Smart Content Data Provider for Doctrine ORM Entities in Sulu
A task guide to registering a Sulu Smart Content data provider that exposes Doctrine ORM entities to templates, with SQL pagination and admin-facing configuration.
14 Nov 2025, 02:40 UTC

Desired outcome
Sulu's Smart Content component provides built-in data providers for pages and snippets, but custom Doctrine ORM entities—such as a product catalog—require a registered data provider. The goal is a tagged service that the admin UI references by alias and that Twig can call via sulu_smart_content, while keeping database queries, pagination, and filtering inside SQL.
Prerequisites
- A Sulu project with write access to source files and service configuration (typically
config/services.yaml). - An existing Doctrine ORM entity and repository, for example a
Productentity mapped to a database table. - Command-line access to the project root to run
bin/consolecommands. - Your installed Sulu version, because interface namespaces, method signatures, and
DataItemconstructor arguments differ between Sulu 1.x and 2.x and across minor releases.
Create the provider class
Implement the Smart Content DataProviderInterface in a plain service. The provider receives filter, datasource, tag, and pagination parameters and must return DataItem objects exposing id, title, image, url, and created/modified values so both the frontend template and the admin preview can render results.
<?php
namespace App\SmartContent;
use Sulu\Component\SmartContent\DataProviderInterface;
use Sulu\Component\SmartContent\DataItem;
use App\Repository\ProductRepository;
class ProductDataProvider implements DataProviderInterface
{
private $repository;
public function __construct(ProductRepository $repository)
{
$this->repository = $repository;
}
public function getData(array $options, array $filters, $datasource, array $tags, $limit, $page, $pageSize)
{
$qb = $this->repository->createQueryBuilder('p');
// Apply declared filters here, e.g. category constraints.
if (!empty($filters['categories'])) {
$qb->andWhere('p.category IN (:categories)')
->setParameter('categories', $filters['categories']);
}
// Pagination must happen in SQL, not by loading every row and slicing in PHP.
$qb->setFirstResult(($page - 1) * $pageSize)
->setMaxResults($pageSize);
$items = [];
foreach ($qb->getQuery()->getResult() as $product) {
// Adjust constructor arguments to match the DataItem signature in your version.
$items[] = new DataItem(
$product->getId(),
$product->getName(),
$product->getImageUrl(),
$product->getUrl(),
$product->getCreated(),
$product->getModified()
);
}
return $items;
}
public function getConfiguration()
{
return [
'filters' => [
'categories' => [
'title' => 'Categories',
'type' => 'list',
],
],
'datasources' => [
'products' => [
'title' => 'Products',
],
],
];
}
}
Register the service and tag
Add the service definition and apply the sulu.smart_content.data_provider tag with an alias attribute. That alias becomes the type name used inside templates and the admin editing form.
# config/services.yaml
services:
App\SmartContent\ProductDataProvider:
arguments:
- '@App\Repository\ProductRepository'
tags:
- { name: sulu.smart_content.data_provider, alias: products }
Run the following command from your project root to confirm the container sees the tag and the expected alias:
bin/console debug:container --tag=sulu.smart_content.data_provider
Expected check: the output lists your App\SmartContent\ProductDataProvider service with the alias products. If it does not appear, the tag name or YAML indentation is likely wrong.
Declare admin-facing configuration
The getConfiguration() method tells the Sulu admin which filters and datasource options the Smart Content tab may offer. Undeclared options are silently ignored, so declaring them keeps the backoffice form and stored configuration consistent. In the example above, the admin UI will expose a categories filter and a products datasource.
Render results in Twig
In the page template, call the sulu_smart_content function with the alias. The helper resolves the alias to your provider and returns the items for iteration. The exact return shape varies by Sulu version; commonly it is a result object containing an items property and pagination metadata, so verify against your installed version before relying on a specific property name.
{% set products = sulu_smart_content('products', {
'filters': {
'categories': ['sale']
},
'page': app.request.get('page', 1),
'pageSize': 12
}) %}
{% for product in products %}
{{ product.title }}
View product
{% endfor %}
Because Sulu stores page content in PHPCR-Jackrabbit, the provider usually queries a Doctrine ORM repository instead. Keep that query logic in the provider or a dedicated repository so templates never touch the ORM directly, and do not query PHPCR workspaces from a provider intended for ORM entities, because mixing them can cause session or transaction issues.
Expected checks
| Check | Command / Action | Expected result |
|---|---|---|
| Service registration | bin/console debug:container --tag=sulu.smart_content.data_provider | Provider appears with alias products |
| Frontend rendering | Load the page using the template above | Items show correct title, URL, and pagination links |
| Admin Smart Content tab | Open the page in the Sulu admin | Declared filters and datasources appear; save succeeds without errors |
| SQL pagination | Inspect Symfony profiler or Doctrine logs | Query contains LIMIT/OFFSET; no full table scan followed by PHP slicing |
After changing service definitions or tags, clear the cache from the project root:
bin/console cache:clear
Recovery options
- Service not found: Confirm the tag name is exactly
sulu.smart_content.data_providerand the YAML indentation is correct, then clear the cache and re-run the debug command. - Admin preview breaks: Ensure every
DataItemhas a usable title and URL; missing values can break the admin Smart Content preview even when the frontend template renders fine. - Memory spikes on large catalogs: Verify
setMaxResultsandsetFirstResultare called on the query builder. Remove anyarray_sliceor manual PHP pagination. - Rollback: Remove the service definition and tag from
config/services.yaml, then runbin/console cache:clear. The page template will no longer resolve theproductsalias, so remove or replace thesulu_smart_contentcall as well.
Limitations and version sensitivity
Exact interface namespaces, method signatures, tag names, and Twig helper return shapes differ between Sulu 1.x and 2.x and across minor releases. Treat the skeleton above as a pattern rather than copy-paste code: open your vendor directory and confirm the DataProviderInterface and DataItem definitions for your installed version before finalizing the class.
Results may be cached per request, so avoid expensive queries inside the provider without a repository-level cache or index. Finally, because this provider is intended for Doctrine ORM entities, do not attempt to read PHPCR page or snippet data from the same service; use the built-in providers for those content types instead.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.