cakephp/cakephp · warning · PageOutOfBoundsException
Page number ` ` could not be found.
Error message
Page number `%s` could not be found.
What it means
After fetching the items and counts, the paginator compares the requested page number (from request params) to the computed current page (total count / page size). If the user asked for a page beyond the last existing page, PageOutOfBoundsException is thrown; its message renders the requested page number.
Solutions
- Wrap paginate() in try/catch for PageOutOfBoundsException and redirect to page 1 (the standard CakePHP exception-renderer behavior).
- Validate/sanitize the requested page before paginating and clamp it to an available range when you can count results beforehand.
- In the exception handler, map PageOutOfBoundsException to a 404 so clients stop crawling invalid pages.
- Regenerate pagination links from the current paging params instead of persisting absolute page numbers.
Example fix
// before
$results = $paginator->paginate($query, $this->request->getQueryParams());
// after
try {
$results = $paginator->paginate($query, $this->request->getQueryParams());
} catch (PageOutOfBoundsException $e) {
$params = $this->request->getQueryParams();
unset($params['page']);
return $this->redirect(['?' => $params]);
} Defensive patterns
Strategy: try-catch
Validate before calling
$page = max(1, (int)($params['page'] ?? 1));
if ($page > 10000) { // sanity cap
$page = 1;
}
$params['page'] = $page;
$results = $paginator->paginate($query, $params); Try / catch
try {
$results = $paginator->paginate($query, $params);
} catch (\Cake\Datasource\Exception\PageOutOfBoundsException $e) {
unset($params['page']);
$results = $paginator->paginate($query, $params); // or redirect to page 1
} Prevention
- Catch PageOutOfBoundsException centrally (exception renderer -> 404).
- Drop or clamp the page param when filters/search terms change.
- Cap the maximum page number accepted from requests to stop crawler abuse.
When it happens
Trigger: Requesting ?page=99 when only 3 pages of results exist; requesting page > 1 on an empty or filtered-down result set; stale pagination links after records were deleted; a page parameter supplied as a huge number.
Common situations: Users bookmarking deep pagination links that become invalid after data changes; bots crawling page=1..N indefinitely; after narrowing a search filter, the retained page number exceeds the new page count.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Invalid sortable field value type for key
- No repository set for query.
- The `order` config must be an associative array. Found…
- A named route was found for
- A required argument cannot follow an optional one
AI-assisted analysis of cakephp/cakephp@1128eba9b0 (2026-09-12).
Data as JSON: /api/errors/d8259cbca293b9b2.
Report an issue: GitHub.
Appendix: source
Thrown at src/Datasource/Paging/NumericPaginator.php:257
}
assert(
$target instanceof RepositoryInterface,
'Pagination target must be an instance of `' . QueryInterface::class
. '` or `' . RepositoryInterface::class . '`.',
);
$data = $this->extractData($target, $params, $settings);
$query = $this->getQuery($target, $query, $data);
$countQuery = clone $query;
$items = $this->getItems($query, $data);
$this->pagingParams['count'] = count($items);
$this->pagingParams['totalCount'] = $this->getCount($countQuery, $data);
$pagingParams = $this->buildParams($data);
if ($pagingParams['requestedPage'] > $pagingParams['currentPage']) {
throw new PageOutOfBoundsException([
'requestedPage' => $pagingParams['requestedPage'],
'pagingParams' => $pagingParams,
]);
}
return $this->buildPaginated($items, $pagingParams);
}
/**
* Build paginated result set.
*
* @param \Cake\Datasource\ResultSetInterface<int, mixed> $items
* @param array $pagingParams
* @return \Cake\Datasource\Paging\PaginatedInterface<int, mixed>
*/
protected function buildPaginated(ResultSetInterface $items, array $pagingParams): PaginatedInterface
{
return new PaginatedResultSet($items, $pagingParams);View on GitHub (pinned to 1128eba9b0)