BookStackApp/BookStack · error · ThemeModuleException
Failed to load extract files from module ZIP with error: {$e
Error message
Failed to load extract files from module ZIP with error: {$exception->getMessage()} What it means
ThemeModuleManager::addFromZip wraps failures from $zip->extractTo(): if extraction of a module ZIP throws, the partially extracted folder is removed and a new ThemeModuleException is thrown embedding the original error message. It indicates the ZIP contents could not be written to disk under the themes modules folder.
Source
Thrown at app/Theming/ThemeModuleManager.php:60
/**
* @throws ThemeModuleException
*/
public function addFromZip(string $name, ThemeModuleZip $zip): ThemeModule
{
$baseFolderName = Str::limit(Str::slug($name), 40, '');
$folderName = $baseFolderName;
while (!$baseFolderName || file_exists($this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName)) {
$folderName = ($baseFolderName ?: 'mod') . '-' . Str::random(4);
}
$folderPath = $this->modulesFolderPath . DIRECTORY_SEPARATOR . $folderName;
try {
$zip->extractTo($folderPath);
} catch (ThemeModuleException $exception) {
if (is_dir($folderPath)) {
$this->deleteDirectoryRecursively($folderPath);
}
throw new ThemeModuleException("Failed to load extract files from module ZIP with error: {$exception->getMessage()}");
}
$module = $this->loadFromFolder($folderName);
if (!$module) {
throw new ThemeModuleException("Failed to load module from zip file after extraction");
}
return $module;
}
protected function deleteDirectoryRecursively(string $path): void
{
$items = array_diff(scandir($path), ['.', '..']);
foreach ($items as $item) {
$itemPath = $path . DIRECTORY_SEPARATOR . $item;
if (is_dir($itemPath)) {
$this->deleteDirectoryRecursively($itemPath);
} else {View on GitHub (pinned to 18f8469a1c)
Solutions
- Check storage permissions on the themes modules folder (web-server user needs write access) and retry the upload
- Re-download or rebuild the ZIP and verify it opens with unzip -t
- Free disk space if the volume is full
- Read the embedded {$exception->getMessage()} in the message — it names the underlying cause (e.g. specific entry or path)
- Confirm the PHP zip extension is installed and current
Example fix
// before (debugging) // message: Failed to load extract files from module ZIP with error: ... // after (server check) sudo -u www-data test -w themes/modules && echo writable unzip -t my-module.zip
Defensive patterns
Strategy: try-catch
Validate before calling
$zipPath = $uploadedFile->getRealPath();
$zipArchive = new ZipArchive();
if ($zipArchive->open($zipPath) !== true || $zipArchive->numFiles === 0) {
throw new InvalidArgumentException('ZIP is corrupt or empty');
}
if (disk_free_space(dirname($modulesFolderPath)) < 10 * 1024 * 1024) {
throw new RuntimeException('Insufficient disk space');
}
if (!is_writable($modulesFolderPath)) {
throw new RuntimeException('Modules folder not writable');
} Type guard
function isUsableZip(string $path): bool {
$z = new ZipArchive();
return $z->open($path) === true && $z->numFiles > 0;
} Try / catch
try {
$module = $manager->addFromZip($name, $zip);
} catch (ThemeModuleException $e) {
Log::error('Module ZIP extraction failed', ['reason' => $e->getMessage()]);
// surface $e->getMessage() to the uploader; cleanup already handled by manager
} Prevention
- Verify uploaded ZIPs with unzip -t or ZipArchive before installing
- Ensure the modules storage folder is writable by the web-server user
- Monitor disk space on the storage volume
- Keep the PHP zip extension updated; test with real module ZIPs in CI
When it happens
Trigger: Installing a module via ThemeModuleZip::extractTo where the zip is corrupt/encrypted, an entry name is unsafe, the target path isn't writable, or disk space is exhausted. Raised through the 'handle' upload flow.
Common situations: Truncated or re-saved ZIP downloads; read-only storage directory after deployment/permission changes; full disk; malicious or malformed zip entries rejected by the extractor; PHP zip extension limitations.
Related errors
- Failed to delete file at "{$itemPath}"
- Failed to load module from zip file after extraction
- errors.chapter_not_found
- errors.page_not_found
- User does not have permission to create a chapter within the
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/15a67c944ab74bb3.
Report an issue: GitHub.