Features
Collections
Collections define content schemas (EAV). ContentCollectionController manages collection metadata; FieldController drives the fields builder (reorder, duplicate, form visibility, layout width, form layout tabs/sections). Pages live under resources/js/pages/collections/collections/.
Authorization
routes/collections.php uses auth + verified plus Spatie permission: middleware on every action (can-*-collections; bulk authorize in FormRequest). The React UI (useCan) and AI tools share the same names. Item read/write also applies CollectionPermissionEnforcer (role collection_permissions field ACL / item_filter). Details: Effective permissions, Collections API. Data shape: Collections data model. Field types: Field types.
Controllers & pages
| Layer | Location |
|---|---|
| Collection controller | App\Http\Controllers\Collections\ContentCollectionController |
| Field controller | App\Http\Controllers\Collections\FieldController |
| Model | App\Models\Collection (Spatie Sortable, soft deletes) |
| Field model | App\Models\CollectionField (sortable, scoped per collection) |
| Routes | routes/collections.php |
| Pages | resources/js/pages/collections/collections/index.tsx, show.tsx, fields.tsx |
Collection CRUD
| Action | Route | Inertia / behavior |
|---|---|---|
| Index | GET /collections | collections/collections/index; toolbar search (name/slug), sort (name/slug/updated_at), direction (asc/desc); ?trashed=1 → onlyTrashed(); drawer deep-links ?edit={id} / ?new=1 |
| Store | POST /collections | Creates collection; if is_singleton, seeds an empty item |
| Show | GET /collections/{collection} | Non-singleton → redirect to items index; singleton → editor (show) |
| Update | PUT /collections/{collection} | Metadata via UpdateContentCollectionRequest |
| Bulk | POST /collections/bulk | action: delete | restore | force_delete + ids |
| Create from pack | POST /collections/packs/{pack} | Collection packs (seo, articles, …) — see Field types |
| Soft-delete | DELETE /collections/{collection} | Soft-delete |
| Restore | POST /collections/{collection}/restore | Restore |
| Force-delete | DELETE /collections/{collection}/force | Permanent delete |
Slug
- On create (
StoreContentCollectionRequest):slugoptional; empty →Str::slug(name); uniqueness viaUniqueCollectionSlugGenerator. - On update: slug regenerated uniquely from name/slug as needed.
is_singleton
- Boolean fillable on
Collection. - Store coerces with
$request->boolean('is_singleton'). - Cannot change after create (422 on update attempts).
- Singleton create seeds
$collection->items()->create([]). - Singleton show uses assembled
singletonRawData+relatedCollections. Content upsert is documented under Collection items.
Sortable
Collection implements Spatie’s sortable trait:
- Order column:
sort_order sort_when_creating=true- Listing uses
Collection::ordered()
Soft-delete cascade to items
Collection::booted():
| Collection event | Items |
|---|---|
| Soft-delete | $collection->items()->delete() |
| Force-delete | $collection->items()->withTrashed()->forceDelete() |
| Restore | $collection->items()->onlyTrashed()->restore() |
Fields builder
Route prefix under each collection:
| Action | Route name | Behavior |
|---|---|---|
| Builder UI | GET …/fields → collections.fields.index | Inertia collections/collections/fields; deep-links ?field={id} / ?newField=1 |
| Create field | POST …/fields | Includes translatable boolean |
| Update field | PATCH …/fields/{field} | Definition update |
| Delete field | DELETE …/fields/{field} | Hard delete |
| Reorder | POST …/fields/reorder | CollectionField::setNewOrder($ids); persists settings.layout_starts_new_row from starts_new_row_ids |
| Duplicate | POST …/fields/{field}/duplicate | replicate without sort_order; name {base}_copy / _copy_N |
| Toggle form visibility | POST …/fields/{field}/toggle-form-visibility | Toggles settings.hidden_in_form |
| Layout width | POST …/fields/{field}/layout-width | Sets settings.layout_width |
| Form layout | PUT …/form-layout | Tabs/sections JSON |
| Field pack | POST …/field-packs/{pack} | Apply pack (skip existing names) — Field packs |
UI: Add field pack… on the fields page; Create from pack… on the collections index.
Collection permissions (enum)
can-show-collections
can-create-collections
can-edit-collections
can-delete-collections
can-restore-collections
can-force-delete-collections
Sidebar show is gated by can-show-collections. Index trash actions use delete/restore/force-delete checks. Field and item pages currently do not wrap every control in useCan.
Source map
| Concern | Location |
|---|---|
| Collection controller | app/Http/Controllers/Collections/ContentCollectionController.php |
| Field controller | app/Http/Controllers/Collections/FieldController.php |
| Routes | routes/collections.php |
| Pages | resources/js/pages/collections/collections/* |