filex
Async file uploads with Dropzone.js, temp-first pattern, chunked uploads, security scanning, and validation rules.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Async file uploads with Dropzone.js, temp-first pattern, chunked uploads, security scanning, and validation rules.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | filex |
| description | Async file uploads with Dropzone.js, temp-first pattern, chunked uploads, security scanning, and validation rules. |
Activate this skill when:
Filex uses a temp-first upload pattern. This is NOT optional — it is how the package works:
temp/ on the local disktemp/abc123_photo.jpg), NOT file objectsThis means: form fields for Filex uploads contain strings, not UploadedFile objects. All validation and controller logic MUST treat them as strings starting with temp/.
ALWAYS include @filexAssets once per page before any <x-filex-uploader> component:
@filexAssets
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<x-filex-uploader name="avatar" />
<button type="submit">Submit</button>
</form>
ALWAYS set the name prop to match the field name your controller expects.
ALWAYS set mimes prop to restrict file types. Never leave it open:
{{-- CORRECT — restricted to specific types --}}
<x-filex-uploader name="photo" mimes="jpeg,png,webp" :max-size="5" />
{{-- WRONG — accepts any file type --}}
<x-filex-uploader name="photo" />
ALWAYS set :max-size to limit file size in MB.
For multiple files, ALWAYS set :multiple="true" and :max-files:
<x-filex-uploader
name="documents"
:multiple="true"
:max-files="10"
:max-size="10"
mimes="pdf,doc,docx"
/>
For edit forms, ALWAYS pass existing files via value prop:
<x-filex-uploader
name="avatar"
mimes="jpeg,png,webp"
:max-size="5"
:value="$user->avatar"
/>
{{-- Multiple files --}}
<x-filex-uploader
name="documents"
:multiple="true"
:value="$post->documents"
/>
enctype="multipart/form-data" is not required — Filex handles uploads via AJAX before form submission, so the form only submits string paths. Adding it is harmless but unnecessary.
For image uploads, use dimension constraints when the design requires specific sizes:
<x-filex-uploader
name="banner"
mimes="jpeg,png"
:max-size="5"
dimensions="min_width=1200,min_height=630"
/>
ALWAYS use the HasFilex trait in controllers that handle file uploads:
use DevWizard\Filex\Traits\HasFilex;
class PostController extends Controller
{
use HasFilex;
}
ALWAYS validate temp paths with ValidFileUpload rules before moving. Do NOT rely on a simple starts_with:temp/ check — it is vulnerable to path traversal attacks (e.g., temp/../../logs/laravel.log):
use DevWizard\Filex\Rules\ValidFileUpload;
$request->validate([
'avatar' => ['required', ValidFileUpload::forImages(maxSizeMB: 5)],
'document' => ['required', ValidFileUpload::forDocuments(maxSizeMB: 10)],
]);
For multiple file fields, ALWAYS validate both the array and each item:
$request->validate([
'documents' => ['required', 'array', 'max:10'],
'documents.*' => [ValidFileUpload::forDocuments(maxSizeMB: 10)],
]);
Use the trait's moveFile() / moveFiles() methods — NEVER manually copy or move temp files:
// Single file — returns ?string (path or null)
$path = $this->moveFile($request, 'avatar', 'avatars');
// Multiple files — returns array of paths
$paths = $this->moveFiles($request, 'documents', 'post-documents');
ALWAYS specify visibility explicitly for security-sensitive files:
// Public files (accessible via URL)
$path = $this->moveFilePublic($request, 'avatar', 'avatars');
// Private files (no public URL, requires signed URL or streaming)
$path = $this->moveFilePrivate($request, 'contract', 'contracts');
To use a specific storage disk (like S3), pass it as the 4th argument:
$path = $this->moveFile($request, 'avatar', 'avatars', 's3');
$paths = $this->moveFiles($request, 'documents', 'docs', 's3');
Store the returned path in the model, not the temp path:
// CORRECT
$avatarPath = $this->moveFile($request, 'avatar', 'avatars');
$post = Post::create([
'title' => $request->title,
'avatar' => $avatarPath, // Stores: "avatars/abc123_photo.jpg"
]);
// WRONG — stores temp path which expires
$post = Post::create([
'avatar' => $request->avatar, // Stores: "temp/abc123_photo.jpg"
]);
Use the Filex facade for file operations outside controllers (services, jobs, commands):
use DevWizard\Filex\Facades\Filex;
// Single file
$result = Filex::moveFile($tempPath, 'avatars');
if ($result->isSuccess()) {
$permanentPath = $result->getPath();
}
// Multiple files
$result = Filex::moveFiles($tempPaths, 'documents', 's3', 'private');
$successfulPaths = $result->getPaths();
$failedItems = $result->getFailed();
ALWAYS check FilexResult for errors when using the facade directly:
$result = Filex::moveFiles($tempPaths, 'uploads');
if (!$result->isAllSuccess()) {
$errors = $result->getErrorMessages();
// Handle failed uploads
Log::warning('Upload failures', ['errors' => $errors]);
}
$paths = $result->getPaths(); // Only successful paths
Blade view:
@filexAssets
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<div>
<x-filex-uploader
name="featured_image"
label="Featured Image"
:required="true"
:max-size="5"
mimes="jpeg,png,webp"
help-text="Recommended: 1200x630px"
/>
@error('featured_image') <span class="text-red-500">{{ $message }}</span> @enderror
</div>
<div>
<x-filex-uploader
name="attachments"
label="Attachments"
:multiple="true"
:max-files="5"
:max-size="10"
mimes="pdf,doc,docx,xlsx,zip"
/>
@error('attachments') <span class="text-red-500">{{ $message }}</span> @enderror
@error('attachments.*') <span class="text-red-500">{{ $message }}</span> @enderror
</div>
<button type="submit">Create Post</button>
</form>
Controller:
use DevWizard\Filex\Traits\HasFilex;
use DevWizard\Filex\Rules\ValidFileUpload;
class PostController extends Controller
{
use HasFilex;
public function store(Request $request)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'featured_image' => ['required', ValidFileUpload::forImages(5)],
'attachments' => ['nullable', 'array', 'max:5'],
'attachments.*' => [ValidFileUpload::forDocuments(10)],
]);
$imagePath = $this->moveFilePublic($request, 'featured_image', 'posts/images');
$attachmentPaths = $this->moveFilesPrivate($request, 'attachments', 'posts/attachments');
$post = Post::create([
'title' => $validated['title'],
'featured_image' => $imagePath,
'attachments' => $attachmentPaths,
]);
return redirect()->route('posts.show', $post);
}
}
Controller:
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'featured_image' => ['nullable', ValidFileUpload::forImages(5)],
'attachments' => ['nullable', 'array', 'max:5'],
'attachments.*' => [ValidFileUpload::forDocuments(10)],
]);
$data = ['title' => $validated['title']];
// Only move file if a new one was uploaded (field contains a temp/ path)
if ($request->filled('featured_image') && str_starts_with($request->featured_image, 'temp/')) {
$data['featured_image'] = $this->moveFilePublic($request, 'featured_image', 'posts/images');
}
if ($request->filled('attachments')) {
$data['attachments'] = $this->moveFilesPrivate($request, 'attachments', 'posts/attachments');
}
$post->update($data);
return redirect()->route('posts.show', $post);
}
Blade view (edit):
<x-filex-uploader
name="featured_image"
label="Featured Image"
:max-size="5"
mimes="jpeg,png,webp"
:value="$post->featured_image"
/>
<x-filex-uploader
name="attachments"
:multiple="true"
:max-files="5"
:max-size="10"
mimes="pdf,doc,docx"
:value="$post->attachments"
/>
| Method | Allowed Types | Default Max |
|---|---|---|
ValidFileUpload::forImages(5) | jpg, jpeg, png, gif, webp | 5 MB |
ValidFileUpload::forDocuments(10) | pdf, doc, docx, xls, xlsx, ppt, pptx, txt, rtf | 10 MB |
FileRule::forArchives(50) | zip, rar, 7z, tar, gz | 50 MB |
FileRule::forAudio(20) | mp3, wav, flac, ogg | 20 MB |
FileRule::forVideo(100) | mp4, avi, mov, mkv | 100 MB |
FileRule::forType('csv', 'text/csv', 10) | Custom single type | Custom |
FileRule::custom([exts], [mimes], maxMB) | Custom multiple types | Custom |
use DevWizard\Filex\Support\FilexRule;
$request->validate([
'file' => [
FilexRule::file(), // File exists and readable
FilexRule::mimes('jpeg,png,pdf'), // Allowed extensions
FilexRule::max(10485760), // Max bytes
FilexRule::image(), // Must be valid image
FilexRule::dimensions('min_width=100,ratio=16/9'), // Dimension constraints
],
]);
$request->validate([
'file' => ['filex_file', 'filex_mimes:jpeg,png', 'filex_max:10485760', 'filex_image'],
]);
config/filex.php)'storage' => [
'disks' => ['default' => 'public', 'temp' => 'local'],
'max_file_size' => 10, // MB — global default
'temp_expiry_hours' => 24, // Hours before temp files are cleaned
'visibility' => ['default' => 'public'],
],
'upload' => [
'chunk' => ['size' => 1048576, 'max_retries' => 3, 'timeout' => 30000],
],
'routes' => [
'prefix' => 'filex', // URL prefix for upload routes
'middleware' => [], // Additional middleware for routes
],
'security' => [
'suspicious_detection' => ['enabled' => true, 'quarantine_enabled' => true],
],
php artisan filex:install # Publish config and assets
php artisan filex:cleanup-temp # Clean expired temp files
php artisan filex:cleanup-temp --dry-run # Preview cleanup
php artisan filex:optimize # Performance tools
php artisan filex:info # Package info
| Anti-Pattern | Correct Pattern |
|---|---|
Storing temp/ paths in database | ALWAYS moveFile() first, store the returned permanent path |
Using $request->file('avatar') | Filex fields are strings, use $request->avatar or moveFile($request, 'avatar', ...) |
Using Laravel's store() / storeAs() on Filex fields | Use $this->moveFile() or Filex::moveFile() |
| Skipping validation on temp paths | ALWAYS validate with ValidFileUpload rules to prevent path traversal |
Omitting @filexAssets in layout | Component JS/CSS won't load, uploads will silently fail |
No mimes restriction on uploader | ALWAYS set mimes to prevent unrestricted file types |
Manual Storage::move() on temp files | Use HasFilex::moveFile() — it handles metadata, security, and atomic writes |
Using required validation for edit forms | Use nullable for file fields on update — value is only present when changed |