{"record":{"id":"fd078da5ce166878","repo":"BookStackApp/BookStack","slug":"parent-book-required-for-chapter-import","errorCode":null,"errorMessage":"Parent book required for chapter import.","messagePattern":"Parent book required for chapter import\\.","errorType":"validation","errorClass":"ZipImportException","httpStatus":null,"severity":"error","filePath":"app/Exports/ZipExports/ZipImportRunner.php","lineNumber":74,"sourceCode":"        $errors = (new ZipExportValidator($reader))->validate();\n        if ($errors) {\n            throw new ZipImportException([\n                trans('errors.import_validation_failed'),\n                ...$errors,\n            ]);\n        }\n\n        try {\n            $exportModel = $reader->decodeDataToExportModel();\n        } catch (ZipExportException $e) {\n            throw new ZipImportException([$e->getMessage()]);\n        }\n\n        // Validate parent type\n        if ($exportModel instanceof ZipExportBook && ($parent !== null)) {\n            throw new ZipImportException([\"Must not have a parent set for a Book import.\"]);\n        } else if ($exportModel instanceof ZipExportChapter && !($parent instanceof Book)) {\n            throw new ZipImportException([\"Parent book required for chapter import.\"]);\n        } else if ($exportModel instanceof ZipExportPage && !($parent instanceof Book || $parent instanceof Chapter)) {\n            throw new ZipImportException([\"Parent book or chapter required for page import.\"]);\n        }\n\n        $this->ensurePermissionsPermitImport($exportModel, $parent);\n\n        if ($exportModel instanceof ZipExportBook) {\n            $entity = $this->importBook($exportModel, $reader);\n        } else if ($exportModel instanceof ZipExportChapter) {\n            $entity = $this->importChapter($exportModel, $parent, $reader);\n        } else {\n            $entity = $this->importPage($exportModel, $parent, $reader);\n        }\n\n        $this->references->replaceReferences();\n\n        $reader->close();\n        $this->cleanup();","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Exports/ZipExports/ZipImportRunner.php#L56-L92","documentation":"ZipImportRunner::run() requires that a chapter export (ZipExportChapter) is imported with a parent that is a Book instance. If $parent is null or any other entity type, it throws ZipImportException 'Parent book required for chapter import.' Chapters can only live directly under books in BookStack's hierarchy.","triggerScenarios":"Importing a chapter ZIP with no parent selected, or with a Chapter/Page/Shelf passed as parent.","commonSituations":"API scripts forgetting parent_id for chapter imports; selecting the wrong parent in the import UI; automation that treats chapters like books.","solutions":["Pass a valid Book id as parent_id in the import API request.","Select a Book as the parent in the import form.","Fetch the correct book id (e.g. via the search/API) before running the import."],"exampleFix":"// before\nPOST /import {\"zip_id\": 7}\n// after\nPOST /import {\"zip_id\": 7, \"parent_id\": 12} // 12 = a Book id","handlingStrategy":"validation","validationCode":"$data = json_decode($zip->getFromName('data.json') ?: '', true);\nif (isset($data['chapter']) && (!$parent instanceof \\BookStack\\Entities\\Models\\Book)) {\n    throw new \\InvalidArgumentException('Chapter imports require a Book parent');\n}","typeGuard":"function chapterParentOk($parent): bool {\n    return $parent instanceof \\BookStack\\Entities\\Models\\Book;\n}","tryCatchPattern":"try {\n    $runner->run($import, $parent);\n} catch (\\BookStack\\Exceptions\\ZipImportException $e) {\n    // resolve a Book id and retry the import\n}","preventionTips":["Resolve and verify the target book id before importing chapters.","UI: always pick a Book (not another chapter) as parent.","API scripts: validate parent entity type via the API before calling import."],"tags":["zip-import","bookstack","parent-validation","api"],"backgroundTag":"import-parent-type-mismatch","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}