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
- Verify the identifier resolves to a Book visible to the user (query the book's ID or slug first).
- Ensure you pass the book's ID, not a chapter's or page's.
- Confirm the target book isn't soft-deleted and the user has view permission on it.
- 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
- Pass book IDs, never chapter/shelf/page IDs
- Resolve and inspect the entity before moving
- Confirm the target is not soft-deleted
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
- Book or chapter to move page into not found
- Selected parent is not a book or chapter
- auth.registration_email_domain_invalid
- errors.chapter_not_found
- errors.page_not_found
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/4ebbe4c97e29ff45.
Report an issue: GitHub.