BookStackApp/BookStack · error · ZipImportException
Selected parent is not a book or chapter
Error message
Selected parent is not a book or chapter
What it means
ImportRepo::runImport supports optional parent context for page and chapter imports. If a parent identifier is supplied but resolves to something other than a Book or Chapter, it throws ZipImportException(['Selected parent is not a book or chapter']). This validates import targets before the DB transaction starts.
Source
Thrown at app/Exports/ImportRepo.php:125
$import->path = $path;
$import->save();
Activity::add(ActivityType::IMPORT_CREATE, $import);
return $import;
}
/**
* @throws ZipImportException
*/
public function runImport(Import $import, ?string $parent = null): Entity
{
$parentModel = null;
if ($import->type === 'page' || $import->type === 'chapter') {
$parentModel = $parent ? $this->entityQueries->findVisibleByStringIdentifier($parent) : null;
if ($parentModel && !($parentModel instanceof Book || $parentModel instanceof Chapter)) {
throw new ZipImportException(['Selected parent is not a book or chapter']);
}
}
DB::beginTransaction();
try {
$model = $this->importer->run($import, $parentModel);
} catch (ZipImportException $e) {
DB::rollBack();
$this->importer->revertStoredFiles();
throw $e;
}
DB::commit();
$this->deleteImport($import);
Activity::add(ActivityType::IMPORT_RUN, $import);
return $model;
}View on GitHub (pinned to 18f8469a1c)
Solutions
- Pass a Book or Chapter ID/slug as the parent (or omit $parent entirely to import at root).
- Resolve and type-check the parent before calling runImport.
- Ensure the importing user has view permission on the target parent so it resolves.
- Catch ZipImportException and read its message list to surface the validation failure to the user.
Example fix
// before
$importRepo->runImport($import, $shelfSlug); // shelf is invalid
// after
$parent = $entityQueries->findVisibleByStringIdentifier($parentId);
if ($parent instanceof Book || $parent instanceof Chapter) {
$importRepo->runImport($import, $parentId);
} else {
$importRepo->runImport($import); // import to root
} Defensive patterns
Strategy: validation
Validate before calling
$parent = $entityQueries->findVisibleByStringIdentifier($parentId);
if ($parent && !($parent instanceof \BookStack\Entities\Book || $parent instanceof \BookStack\Entities\Chapter)) {
throw new \InvalidArgumentException('Import parent must be a Book or Chapter');
} Type guard
function validImportParent(?object $e): bool {
return $e === null || $e instanceof \BookStack\Entities\Book || $e instanceof \BookStack\Entities\Chapter;
} Try / catch
try {
$importRepo->runImport($import, $parentId);
} catch (\BookStack\Exceptions\ZipImportException $e) {
return back()->withErrors(['parent' => implode(', ', $e->getMessages())]);
} Prevention
- Validate the parent identifier resolves to a Book/Chapter before import
- Omit the parent to import to root when unsure
- Use IDs from the same environment; don't reuse staging identifiers
When it happens
Trigger: runImport($import, $parent) where $parent is a Shelf's or Page's ID/slug, a nonexistent identifier, or an entity the importing user cannot view (findVisibleByStringIdentifier returns null so only a truthy non-Book/Chapter triggers... a null parent is treated as no parent).
Common situations: Automated import pipelines passing shelf IDs as parents; IDs from a staging instance reused in production; slugs that collide with non-book entities; users lacking visibility of the intended parent so lookup yields null and content imports to root instead.
Related errors
- Book to move chapter into not found
- Book or chapter to move page into not found
- 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/e70a89f8541eed35.
Report an issue: GitHub.