BookStackApp/BookStack · error · NotFoundException
errors.page_not_found
Error message
errors.page_not_found
What it means
PageQueries::findVisibleByIdOrFail fetches a page by ID through the visibility-restricted findVisibleById query. When the query returns null — page absent, soft-deleted, or invisible to the current user — it throws NotFoundException with 'errors.page_not_found'. It is the fail-fast wrapper converting 'no visible row' into a 404 exception.
Source
Thrown at app/Entities/Queries/PageQueries.php:42
/**
* @return Builder<Page>
*/
public function start(): Builder
{
return Page::query();
}
public function findVisibleById(int $id): ?Page
{
return $this->start()->scopes('visible')->find($id);
}
public function findVisibleByIdOrFail(int $id): Page
{
$page = $this->findVisibleById($id);
if (is_null($page)) {
throw new NotFoundException(trans('errors.page_not_found'));
}
return $page;
}
public function findVisibleBySlugsOrFail(string $bookSlug, string $pageSlug): Page
{
/** @var ?Page $page */
$page = $this->start()->with('book')
->scopes('visible')
->whereHas('book', function (Builder $query) use ($bookSlug) {
$query->where('slug', '=', $bookSlug);
})
->where('slug', '=', $pageSlug)
->first();
if (is_null($page)) {
throw new NotFoundException(trans('errors.page_not_found'));View on GitHub (pinned to 18f8469a1c)
Solutions
- Confirm the page ID exists and is not in the trash (Page::withTrashed()->find($id) locally or check the Trash screen).
- Verify the user's role has view access or entity-level restrictions allowing the page.
- Catch NotFoundException around the call and handle missing pages gracefully.
- If the page was moved, its ID persists — check the URL/slug rather than recreating content.
Example fix
// before
$page = $pageQueries->findVisibleByIdOrFail($id);
// after
try {
$page = $pageQueries->findVisibleByIdOrFail($id);
} catch (NotFoundException $e) {
Log::warning('Page not visible', ['id' => $id]);
abort(404);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!\BookStack\Entities\Page::where('id', $id)->exists()) {
throw new \InvalidArgumentException("Page {$id} does not exist");
} Type guard
function pageIsVisible(\BookStack\Entities\Queries\PageQueries $q, int $id): bool {
return $q->findVisibleById($id) !== null;
} Try / catch
try {
$page = $pageQueries->findVisibleByIdOrFail($id);
} catch (\BookStack\Exceptions\NotFoundException $e) {
Log::warning('Page missing/invisible', ['id' => $id]);
abort(404);
} Prevention
- Prefer ID lookups over cached IDs that may be stale
- Check permissions first if operating as a specific user
- Restore from trash instead of recreating content
When it happens
Trigger: Calling findVisibleByIdOrFail($id) with an ID that doesn't exist, references a soft-deleted (trashed) page, or a page the current user cannot view due to role/restriction permissions.
Common situations: API integrations holding stale page IDs after deletion; deep links to pages removed during book reorganization; users without view permissions receiving 404 instead of 403; importing content into the wrong instance.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- errors.chapter_not_found
- User does not have permission to create a chapter within the
- User does not have permission to create a page within the ne
- Book to move chapter into not found
- Book or chapter to move page into not found
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/722512caf6282ebf.
Report an issue: GitHub.