BookStackApp/BookStack · error · MoveOperationException

Book or chapter to move page into not found

Error message

Book or chapter to move page into not found

What it means

PageRepo::move resolves the destination via findVisibleByStringIdentifier; pages may be moved into a Chapter or a Book. If the resolved entity is neither (missing, invisible, a Shelf, or another Page), it throws MoveOperationException('Book or chapter to move page into not found').

Source

Thrown at app/Entities/Repos/PageRepo.php:279

        $this->baseRepo->sortParent($page);

        return $page;
    }

    /**
     * Move the given page into a new parent book or chapter.
     * The $parentIdentifier must be a string of the following format:
     * 'book:<id>' (book:5).
     *
     * @throws MoveOperationException
     * @throws PermissionsException
     */
    public function move(Page $page, string $parentIdentifier): Entity
    {
        $parent = $this->entityQueries->findVisibleByStringIdentifier($parentIdentifier);
        if (!$parent instanceof Chapter && !$parent instanceof Book) {
            throw new MoveOperationException('Book or chapter to move page into not found');
        }

        if (!userCan(Permission::PageCreate, $parent)) {
            throw new PermissionsException('User does not have permission to create a page within the new parent');
        }

        return (new DatabaseTransaction(function () use ($page, $parent) {
            $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : null;
            $newBookId = ($parent instanceof Chapter) ? $parent->book->id : $parent->id;
            $this->parentChanger->changeBook($page, $newBookId);
            $page->rebuildPermissions();

            Activity::add(ActivityType::PAGE_MOVE, $page);

            $this->baseRepo->sortParent($page);

            return $parent;
        }))->run();

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Confirm the identifier refers to an existing, visible Book or Chapter (fetch it first and check the type).
  2. Don't pass Shelf or Page identifiers — resolve the chapter/book the page should live in.
  3. Check the target isn't in the trash and the user has view permission on it.
  4. Catch MoveOperationException around move() and prompt the user to pick a valid parent.

Example fix

// before
$pageRepo->move($page, $shelfId); // shelves are not valid page parents
// after
$parent = $entityQueries->findVisibleByStringIdentifier($parentId);
if ($parent instanceof Book || $parent instanceof Chapter) {
    $pageRepo->move($page, $parentId);
}
Defensive patterns

Strategy: validation

Validate before calling

$parent = $entityQueries->findVisibleByStringIdentifier($identifier);
if (!($parent instanceof \BookStack\Entities\Book || $parent instanceof \BookStack\Entities\Chapter)) {
    throw new \InvalidArgumentException('Parent must be a Book or Chapter');
}

Type guard

function isValidPageParent(?object $e): bool {
    return $e instanceof \BookStack\Entities\Book || $e instanceof \BookStack\Entities\Chapter;
}

Try / catch

try {
    $pageRepo->move($page, $identifier);
} catch (\BookStack\Exceptions\MoveOperationException $e) {
    return back()->withErrors(['parent' => $e->getMessage()]);
}

Prevention

When it happens

Trigger: move($page, $parentIdentifier) with a nonexistent identifier, a Shelf or Page ID, a soft-deleted parent, or an identifier for an entity the user cannot view (lookup returns null).

Common situations: Bulk-migration scripts referencing IDs from another environment; passing a shelf ID assuming pages can live on shelves; target chapter deleted before the move; permission restrictions hiding the target so it resolves to null.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/60527c2871af8b32. Report an issue: GitHub.