Concepts
File tree model
The file manager stores a hierarchical tree of file nodes on the assets disk, with content-addressed versions, optional chunked uploads, and async work for large duplicates and multi-file zips.
Related
Product UI: File manager. Config keys: Files & storage. HTTP: Admin & files API.
File node types
Enum: App\Enums\FileTypeEnum (app/Enums/FileTypeEnum.php).
| Case | Value | Notes |
|---|---|---|
| File | file | Regular uploaded blob (current UI create path) |
| Folder | folder | Container node (current UI create path) |
| Link | link | Reserved in enum |
| External | external | Reserved in enum |
| Generated | generated | Reserved in enum |
| Collection | collection | Reserved in enum |
Model: App\Models\File — hierarchical via parent_id / children, soft deletes, Spatie HasTags.
Current create paths
FileService / controller create flows and the TypeScript AdminFileRow.type currently use file and folder only. The other enum values exist for forward compatibility but are not wired into today’s create UI.
Versions
| Piece | Detail |
|---|---|
| Model | App\Models\FileVersion |
| Table | file_versions |
| Pointer | files.current_version_id → File::currentVersion() |
| Relation | File::versions() |
| Dedup | FileService::resolveOrCreateVersion() reuses a version with the same content hash when possible |
Version fields include disk, storage_path, hash, mime_type, size, width, height, and meta. Upload and replace flows update current_version_id to the resolved version.
Chunked uploads
Large uploads go through a session model rather than a single request body.
| Piece | Detail |
|---|---|
| Model | App\Models\FileUpload |
| Table | file_uploads |
| Service | FileService::initChunkUpload / uploadChunk / complete-and-merge |
| Default disk | assets |
| Session TTL | 24 hours from init |
| Cleanup | files:cleanup-uploads (hourly schedule) |
Routes (under can.manage.files in routes/admin.php):
| Method | Path name | Purpose |
|---|---|---|
| POST | files.uploads.init | Start session |
| POST | files.uploads.chunk | Upload a chunk |
| POST | files.uploads.complete | Merge and create/update file |
| GET | files.uploads.status | Poll session status |
fileables / HasFiles
Polymorphic attachments let other models own files without owning the blob tree.
| Piece | Path / fact |
|---|---|
| Trait | App\Traits\HasFiles |
| Pivot | fileables (file_id, fileable_id, fileable_type, role, order) |
| Trait API | files(), attachFile, detachFile, updateFile, syncFiles, filesByRole, firstFileByRole |
| HTTP | POST files/{file}/attach, POST files/{file}/detach |
| Validation | FileService attach/detach requires the target class to use HasFiles |
Opt-in trait
No Eloquent model currently use HasFiles. The pivot, trait, and service endpoints are ready; domain models must opt in to become attach targets.
Tags
- Spatie tags via
HasTagsonFile - Sync:
FileService::syncTags() - Route:
PUT files/{file}/tags(files.tags) - Permission:
can-tag-files - Middleware maps
files.tagsand bulktag/untagactions inEnsureCanManageFiles
Favorites
- Pivot:
file_favorites(user_id,file_id) - Relations / service:
File::favoritedBy(),FileService::favorite/unfavorite/bulkFavorite - Routes:
POST/DELETEfiles/{file}/favorite - Permission:
can-favorite-files - API resource exposes
is_favorited
Disks (assets)
Configured in config/filesystems.php as disk assets:
| Setting | Value |
|---|---|
| Driver | local |
| Root | storage/app/public/assets |
| URL | {FILE_PUBLIC_BASE}/storage/assets |
| Visibility | public |
Default for files.disk, file_uploads.disk, and FileService create/upload paths.
Thumbnails — FileTransformService
Class: App\Services\FileTransformService.
| Behavior | Value |
|---|---|
| Default size | 128 px |
| Max size | 256 px |
| Cleanup sizes | 64, 128, 256 |
| Eligible | type = file, mime_type starts with image/, not SVG |
| Fit | Contain-fit resize (GD) |
| Format | WebP q82 if available, else JPEG q82 |
| Cache path | {dirname(storage_path)}/transforms/{sha256}.webp|jpg on the same file disk |
| HTTP | GET files/{file}/thumbnail → files.thumbnail |
| Resource | thumbnail_url when isImage() |
Zip and duplicate thresholds
From config/files.php:
| Config key | Env | Default | Meaning |
|---|---|---|---|
files.duplicate_sync_max_bytes | FILES_DUPLICATE_SYNC_MAX_BYTES | 52428800 (50 MiB) | Single non-folder files at or below this size duplicate synchronously; folders, bulk selections, and larger files queue DuplicateFilesJob |
files.zip_ttl_minutes | FILES_ZIP_TTL_MINUTES | 60 | Prepared zips under storage/app/zips; cleanup / 410 after TTL (files:cleanup-zips) |
files.zip_max_bytes | FILES_ZIP_MAX_BYTES | 104857600 (100 MiB) | Hard max archive size in FileService::buildZipArchive |
Zip is always async for multi-download
Multi-file / folder download always queues PrepareFilesZipJob (HTTP 202). zip_max_bytes is a size cap, not a sync/async threshold. Duplicate sync vs async is the size-based split described above (FileController::shouldDuplicateSynchronously()).
Jobs and cleanup
| Job / command | Role |
|---|---|
DuplicateFilesJob | Async copy + notification |
PrepareFilesZipJob | Build zip + notify download ready |
files:cleanup-uploads | Stale chunk sessions (hourly) |
files:cleanup-zips | Expired zip archives (hourly) |
Related pages
- Architecture — services and jobs
- Effective permissions —
can.manage.files - Files & storage
- AI assistant model —
ManageFilestool