BookStackApp/BookStack · error · ImageUploadException
errors.path_not_writable
Error message
errors.path_not_writable
What it means
ImageUploadException with 'errors.path_not_writable' is thrown by ImageService::saveNew when the storage disk's put() fails while writing new image data to $fullPath. Like the file-upload variant, it wraps any disk exception into a localized message that includes the path BookStack attempted to write.
Source
Thrown at app/Uploads/ImageService.php:93
$fileName = $this->storage->cleanImageFileName($imageName);
$imagePath = '/uploads/images/' . $type . '/' . date('Y-m') . '/';
while ($disk->exists($imagePath . $fileName)) {
$fileName = Str::random(3) . $fileName;
}
$fullPath = $imagePath . $fileName;
if ($secureUploads) {
$fullPath = $imagePath . Str::random(16) . '-' . $fileName;
}
try {
$disk->put($fullPath, $imageData, true);
} catch (Exception $e) {
Log::error('Error when attempting image upload:' . $e->getMessage());
throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $fullPath]));
}
$imageDetails = [
'name' => $imageName,
'path' => $fullPath,
'url' => $this->storage->getPublicUrl($fullPath),
'type' => $type,
'uploaded_to' => $uploadedTo,
];
if (user()->id !== 0) {
$userId = user()->id;
$imageDetails['created_by'] = $userId;
$imageDetails['updated_by'] = $userId;
}
$image = (new Image())->forceFill($imageDetails);
$image->save();View on GitHub (pinned to 18f8469a1c)
Solutions
- Check the Laravel log for 'Error when attempting image upload: ...' for the wrapped cause
- Ensure the gallery/uploads directory exists and is writable (chown www-data, chmod 775)
- Validate the configured storage disk (FILESYSTEM_DISK and its config) including credentials for cloud disks
- Check disk free space/quota
Example fix
// before sudo chmod 700 /var/www/bookstack/storage/app/uploads/gallery // after sudo chown -R www-data:www-data /var/www/bookstack/storage/app/uploads sudo chmod -R 775 /var/www/bookstack/storage/app/uploads
Defensive patterns
Strategy: try-catch
Validate before calling
$dir = dirname(config('filesystems.disks.' . config('filesystems.default') . '.root', storage_path('app')));
if (is_dir($dir) && !is_writable($dir)) {
throw new \RuntimeException("Image storage dir not writable: $dir");
} Try / catch
try {
$image = $imageService->saveNewFromUpload($file, 'gallery', $pageId);
} catch (ImageUploadException $e) {
Log::error('Image save failed: ' . $e->getMessage());
return back()->withErrors(['image' => 'Could not save image; storage target is not writable.']);
} Prevention
- Keep storage ownership/permissions correct after deploys and server moves (deployment script step)
- Run a smoke-test write to the disk in health checks
- Alert on disk usage thresholds
- Validate cloud disk credentials/region on environment setup
When it happens
Trigger: Calling saveNew() directly or via saveNewFromUpload()/saveNewFromBase64Uri() when $disk->put() throws: unwritable gallery directory, disk quota full, misconfigured storage disk (local root missing, s3 credentials invalid).
Common situations: www-data cannot write to uploads/gallery path; FILESYSTEM_DISK switched to s3 with bad credentials; moved instance without copying/migrating storage permissions; full disk on self-hosted servers.
Related errors
- errors.path_not_writable
- errors.image_upload_replace_type
- Failed to load key from file path with error: {$exception->g
- auth.email_confirm_send_error
- auth.registration_email_domain_invalid
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/8eccf14ca073572b.
Report an issue: GitHub.