Features

File manager

The file manager is the admin UI and JSON API under /files/*. FileController delegates to FileService (and transform/jobs for thumbnails, zip, and duplication). Every request passes through can.manage.files, which maps the route (and bulk action) to a specific file permission.

Related

Tree / versions / uploads model: File tree model. Disks & limits: Files & storage. HTTP tables: Admin & files API. Async outcomes land in the notifications bell.

Access & UI

ItemDetail
ControllerApp\Http\Controllers\Admin\FileController
ServiceApp\Services\Files\FileService (plus FileTransformService for thumbnails)
Middleware aliascan.manage.filesApp\Http\Middleware\EnsureCanManageFiles
Routesroutes/admin.php (prefix /files)
Frontendresources/js/pages/admin/files/index.tsx
Sidebar gatecan-show-files

The single Inertia page handles browse, selection, uploads, and bulk actions; many operations also expose JSON endpoints for the same UI.

Public vs private visibility

Each file/folder has an access field (files.access): public, private, or null (inherit from the nearest ancestor). The detail panel Visibility select sets Inherit / Public / Private; folders marked private apply to children unless a child overrides. Admin users always see every file.

Public CMS API (role Files access): read covers public files; read_private unlocks effective-private files. Without read_private, get/content/transform return 403, list omits private rows, and item expanders return null / omit (no id leak). Without read on a public file field expand (include=files), the payload is { "id": N, "access": "denied" } — grant Files → Read on the role. Typical public role: Read ✓, Read private ✕ — see Public CMS API and Roles & permissions.

Leave guard during upload

While any upload is pending / uploading, the files page registers with the admin leave guard. Navigating away shows Upload in progressLeave anyway lets uploads finish in the background (global in-memory store); Keep editing stays on the page. Soft/force delete and bulk delete open a confirm dialog before running.

Open a file’s detail panel via ?file={id} on /files (synced when opening/closing the panel). See Routing — Admin UI deep-links.

Where used (collection references)

The detail panel Where used section calls GET /files/{file}/where-used (files.where-used). FileWhereUsedScanner scans collections_items_values for image / file / files / blocks fields that store this file id (on-demand; ~30s cache; no dedicated DB index in v1).

Each hit links to the item editor (/collections/{id}/items/{item_id}) and shows the field name. Soft-deleted items are ignored.

Soft/force delete confirms still allow Delete anyway when references exist — the dialog shows a reference count when the selected files can be scanned quickly (≤10 files). Open the detail panel for the full list of links.

Limits

Where-used is an on-demand LIKE prefilter + exact id match. Nested block file fields are supported; very large catalogs may feel slow until a dedicated index lands.

Capabilities

CapabilityHow
BrowseGET /files/{folder?} (Inertia) and GET /files/list (JSON, page size 50)
FoldersPOST /files/foldersFileService::createFolder
Direct uploadPOST /files/uploaduploadFile
Chunked uploadPOST /files/uploads/init, …/chunk, …/complete; GET …/status
Replace / versionsPOST /files/{file}/replacereplaceFile / version resolution
CopyPOST /files/{file}/copy — sync or queued (see below)
Move / renamePATCH …/move, PATCH …/rename
MetadataPATCH /files/{file}updateMetadata (includes visibility access)
TagsGET /files/tags, PUT /files/{file}/tags (Spatie tags)
FavoritesPOST/DELETE /files/{file}/favorite
Soft / force deleteDELETE /files/{file}, POST …/restore, DELETE …/force
Where usedGET /files/{file}/where-used → collection item references
Attach / detachPOST …/attach, POST …/detach (polymorphic links)
ThumbnailsGET /files/{file}/thumbnailFileTransformService::ensureThumbnail (images)
BulkPOST /files/bulk — move, delete, restore, force_delete, favorite/unfavorite, tag/untag, copy
Single downloadGET /files/{file}/download
Zip (async)POST /files/downloadPrepareFilesZipJob; download GET /files/zips/{jobId}

Duplicate: sync vs queue

FileController copy behavior:

  • Sync when the source is not a folder and size <= config('files.duplicate_sync_max_bytes', 52428800) (~50 MiB).
  • Queue DuplicateFilesJob otherwise (folders, larger files, or bulk action=copy) → HTTP 202 with { queued: true, job_id }.

Completed/failed duplication notifies via FileDuplicationCompletedNotification / FileDuplicationFailedNotification.

Zip async + notifications

PrepareFilesZipJob builds the archive; the user is notified with:

  • FileZipReadyNotification (type: file_zip_ready) — download via files.zips.download
  • FileZipFailedNotification (type: file_zip_failed)

can.manage.files mapping

EnsureCanManageFiles resolves the required permission with EffectivePermissionResolver, then checks it.

Named route overrides

Route name(s)Permission
files.restorecan-restore-files
files.force-deletecan-force-delete-files
files.download, files.download-many, files.zips.downloadcan-download-files
files.favorite, files.unfavoritecan-favorite-files
files.copycan-copy-files
files.replacecan-replace-files
files.tagscan-tag-files
files.updatecan-update-file-metadata

Bulk action → permission

actionPermission
restorecan-restore-files
force_deletecan-force-delete-files
favorite / unfavoritecan-favorite-files
tag / untagcan-tag-files
copycan-copy-files
deletecan-delete-files
move / defaultcan-edit-files

Default by HTTP method

If no override matches:

MethodPermission
GET / HEADcan-show-files
POSTcan-create-files
PUT / PATCHcan-edit-files
DELETEcan-delete-files

File PermissionEnum values

can-show-files
can-create-files
can-edit-files
can-delete-files
can-restore-files
can-force-delete-files
can-download-files
can-favorite-files
can-copy-files
can-replace-files
can-tag-files
can-update-file-metadata

Route checklist

GET    /files/list
GET    /files/tags
POST   /files/folders
POST   /files/upload
POST   /files/bulk
POST   /files/download
GET    /files/zips/{jobId}
PATCH  /files/{file}
POST   /files/{file}/replace
POST   /files/{file}/copy
POST   /files/{file}/favorite
DELETE /files/{file}/favorite
PUT    /files/{file}/tags
GET    /files/{file}/download
GET    /files/{file}/thumbnail
PATCH  /files/{file}/move
PATCH  /files/{file}/rename
DELETE /files/{file}
POST   /files/{file}/restore
DELETE /files/{file}/force
POST   /files/{file}/attach
POST   /files/{file}/detach
POST   /files/uploads/init
POST   /files/uploads/chunk
POST   /files/uploads/complete
GET    /files/uploads/status
GET    /files/{folder?}          # Inertia index (whereNumber folder)

Jobs

JobNotifications
App\Jobs\PrepareFilesZipJobFileZipReadyNotification, FileZipFailedNotification
App\Jobs\DuplicateFilesJobFileDuplicationCompletedNotification, FileDuplicationFailedNotification

Source map

ConcernLocation
Controllerapp/Http/Controllers/Admin/FileController.php
Middlewareapp/Http/Middleware/EnsureCanManageFiles.php
Serviceapp/Services/Files/FileService.php
Aliasbootstrap/app.phpcan.manage.files
Pageresources/js/pages/admin/files/index.tsx
Previous
Collection items