BookStackApp/BookStack · error · MoveOperationException

Book to move chapter into not found

Error message

Book to move chapter into not found

What it means

ChapterRepo::move resolves the target parent for a chapter move via findVisibleByStringIdentifier (ID or slug). If the resolved entity is not a Book instance — no match found, or the identifier points to a Chapter/Page/Shelf — it throws MoveOperationException('Book to move chapter into not found'). Chapters can only be moved into books.

Source

Thrown at app/Entities/Repos/ChapterRepo.php:94

    {
        $this->trashCan->softDestroyChapter($chapter);
        Activity::add(ActivityType::CHAPTER_DELETE, $chapter);
        $this->trashCan->autoClearOld();
    }

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

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

        return (new DatabaseTransaction(function () use ($chapter, $parent) {
            $this->parentChanger->changeBook($chapter, $parent->id);
            $chapter->rebuildPermissions();
            Activity::add(ActivityType::CHAPTER_MOVE, $chapter);

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

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

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Verify the identifier resolves to a Book visible to the user (query the book's ID or slug first).
  2. Ensure you pass the book's ID, not a chapter's or page's.
  3. Confirm the target book isn't soft-deleted and the user has view permission on it.
  4. Catch MoveOperationException around move() and surface a clear message to the user.

Example fix

// before
$chapterRepo->move($chapter, $someId); // may not be a book
// after
$parent = $entityQueries->findVisibleByStringIdentifier($someId);
if ($parent instanceof Book) {
    $chapterRepo->move($chapter, $someId);
} else {
    throw new \InvalidArgumentException('Target must be a book');
}
Defensive patterns

Strategy: validation

Validate before calling

$parent = $entityQueries->findVisibleByStringIdentifier($parentId);
if (!$parent instanceof \BookStack\Entities\Book::class && !($parent instanceof \BookStack\Entities\Book)) {
    throw new \InvalidArgumentException('Parent must be a visible Book');
}

Type guard

$parent = $entityQueries->findVisibleByStringIdentifier($identifier);
if ($parent instanceof \BookStack\Entities\Book) { /* safe to move */ }

Try / catch

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

Prevention

When it happens

Trigger: Calling move($chapter, $parentIdentifier) where the identifier is a chapter's or shelf's ID/slug, a nonexistent identifier, an identifier the user can't see, or an ID passed as a string type that doesn't resolve.

Common situations: API scripts hardcoding numeric IDs that shifted between environments; passing a chapter ID where a book ID is required; target book deleted beforehand; permission restrictions hiding the target book so lookup returns null.

Related errors


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