Inconsistent pagination limits in Shopware 6 API and custom repository queries
0 reputation · 24 Apr 2023, 15:19 UTC
0 reputation · 24 Apr 2023, 15:19 UTC
When bounding a query and paginating a large dataset in Shopware 6, the goal is to control the number of results returned per page to avoid performance issues.
The repository layer’s paginate() method accepts a Page and Limit but does not expose a global default maximum. Developers must enforce limits manually. The Admin API applies a hard‑coded 1000‑item cap on some endpoints, yet this limit is not consistently enforced on custom queries, leading to ambiguous behavior.
Because of the lack of a documented global cap, it is unclear what the effective maximum page size is across all repositories and API endpoints in a typical Shopware 6 installation.
paginate() enforce a maximum page size internally, and if so, what is that limit?35275 reputation · 24 Apr 2023, 18:35 UTC
No, the DAL repository layer does not enforce a global maximum page size. A Criteria limit is passed through to the generated SQL LIMIT clause essentially as-is, so a custom repository search with setLimit(5000) will attempt to fetch 5000 rows. The 1000-item cap you see is an API-layer concern, not a repository concern, and there is no single configuration switch that sets a global default or maximum page size for all repositories. The practical consequence: if you want a ceiling, you must enforce it yourself in your own code.
Confirmed, well-established behavior:
EntityRepository::search() paginates via Criteria::setLimit() and Criteria::setOffset(). The values flow into the SQL query the DAL generates.limit and page parameters and apply a default limit (commonly 25) when none is given. Clients that omit limit silently get a truncated first page, not the full dataset.limit=500 may return fewer items than requested, while a direct repository call with the same limit honors it. This asymmetry is the usual source of the "inconsistent limits" confusion.Version-dependent: the exact default limit and any hard cap applied by specific Admin API endpoints have changed across Shopware 6 minor releases. Do not assume the 1000-item figure applies to every endpoint or every 6.x version — verify against your installed version's source or by probing the endpoint.
Three DAL behaviors frequently masquerade as a pagination-limit bug:
Criteria::setTotalCountMode(Criteria::TOTAL_COUNT_MODE_EXACT), the reported total may be missing or approximate, so page counts computed from total mismatch the fetched pages.createdAt without adding id as a secondary sort), rows can shift between pages, producing duplicates or gaps that look like a limit problem.$criteria = new Criteria();
$criteria->setLimit(min($requestedLimit, self::MAX_LIMIT)); // enforce your own cap
$criteria->setOffset(($page - 1) * $limit);
$criteria->setTotalCountMode(Criteria::TOTAL_COUNT_MODE_EXACT);
$criteria->addSorting(new FieldSorting('createdAt'), new FieldSorting('id'));Then verify:
limit and page plus a stable sort, and compare item counts and totals.LIMIT/OFFSET actually sent to MySQL matches what your code requested.There is no hidden internal maximum in paginate()/repository search, and no global config for a default or maximum page size. The API's caps live in the API layer and do not protect custom repository queries. Enforce your own maximum in code, use exact total-count mode, and always add a unique secondary sort before treating mismatched pages as a limit bug.
Use comments to ask for clarification. Post a solution as an answer.
35,275 reputation · 25 Apr 2023, 03:14 UTC
While the previous response clarifies that the DAL does not enforce a global maximum, it is important to note that Criteria::setLimit() and setOffset() only provide consistent results if a stable sort order is defined. Without an explicit Criteria::addSorting() call, the database may return rows in a non-deterministic order.
This often manifests as "shifting" data, where the same record appears on both page one and page two, or is skipped entirely during pagination. To ensure deterministic behavior across API and repository queries, always include a unique identifier (like the primary key) as a secondary sort criterion:
$criteria->addSorting(new FieldSorting('createdAt', FieldSorting::DESCENDING));
$criteria->addSorting(new FieldSorting('id', FieldSorting::ASCENDING));
When verifying this in a Shopware 6 environment, check the generated SQL logs to ensure the ORDER BY clause is present and consistent across the different layers of your application.