Features

Collection items

Collection items hold the EAV values for a collection. ItemController covers list/create/edit/soft-delete lifecycle; singletons upsert through PUT /collections/{id}/singleton-content. Validation is built by CollectionItemDataRuleBuilder.

Related

Schema & cascade: Collections. Field type catalog: Field types. Model internals: Collections data model. HTTP table: Collections API.

Authorization

All collection/item routes use Spatie permission: middleware (can-show-collections, can-create-collections, can-edit-collections, …). Super-admin bypasses via Gate::before. See Security checklist.

Controller & pages

ItemDetail
ControllerApp\Http\Controllers\Collections\ItemController
RevisionsApp\Http\Controllers\Collections\ItemRevisionController
Singleton upsertContentCollectionController::upsertSingletonContent
ValidationApp\Services\Collections\CollectionItemDataRuleBuilder
Query / filtersApp\Services\Collections\CollectionItemQueryService
Pagesresources/js/pages/collections/items/index.tsx, form.tsx, revisions.tsx

Item CRUD

ActionRoutePermission
IndexGET /collections/{collection}/itemscan-show-collections
Export CSV/JSONGET /collections/{collection}/items/export?format=csv|jsoncan-show-collections
List columnsPUT /collections/{collection}/list-columnscan-edit-collections
Ask AIToolbar / row actionsOpens FAB with seeded prompt (AskAiButton, can-use-ai) — see AI chat
New formGET /collections/{collection}/items/newcan-create-collections
StorePOST /collections/{collection}/itemscan-create-collections
Show / editGET /collections/{collection}/items/{item}can-show-collections
UpdatePUT …/items/{item}can-edit-collections (PUT only)
BulkPOST …/items/bulkFormRequest per action: delete | restore | force_delete
Soft-deleteDELETE …/items/{item}can-delete-collections
RestorePOST …/items/{item}/restorecan-restore-collections
Force-deleteDELETE …/items/{item}/forcecan-force-delete-collections

Admin field-level rules / item_filter (from the role’s Collection access matrix) also apply when the user is not a super-admin — same dialect as the Public CMS API.

Field ACL in the item form

When the signed-in admin’s effective roles include collection field rules, ItemController (and singleton show) pass a fieldGrants map to the form:

GrantUI
read: false (or field absent from the map)Field hidden
create: false on new itemField read-only (No write access)
update: false on editField read-only
Super-admin / Spatie-only (no collection grant rules)fieldGrants: null — all fields unrestricted in the form

Server-side write checks (assertWritableFields) still apply on save. See Roles & permissions — Collection access rules.

Advanced list filters

The items index toolbar has:

  1. Title search — debounced contains on title (filter[title][_contains]=…)
  2. Filters popover — AND multi-field builder writing the same query dialect as the Public API / GraphQL
QueryMeaning
?filter[title]=helloContains (LIKE), backward compatible
?filter[status][_eq]=publishedExact match
?filter[status][_neq]=draftNot equal
?filter[title][_contains]=helloExplicit contains
?filter[status][_in]=a,b,cIn list
?filter[count][_gte]=1Numeric / comparable ranges (_gt, _lt, _lte)
?filter[body][_null]=1Empty / missing
?filter[body][_nnull]=1Present

Multiple fields are AND-combined. Service: CollectionItemQueryService. Clear all resets the URL filters.

Return to list (filters & page)

Edit / New item links from the items index append a return query param with the current list path + query (filters, sort, page).

  • The form Items breadcrumb uses return when it is a same-origin path under /collections/….
  • Invalid or off-site return values are ignored; the breadcrumb falls back to the plain items index.
  • Drawer-based admin screens (collections / users / groups) do not need return — closing the drawer keeps the list URL.

Export CSV / JSON

From the items index header, Export downloads the current filtered list (same filter / sort / direction query params, plus role item_filter SQL pushdown) as CSV or JSON.

DetailValue
RouteGET /collections/{collection}/items/export?format=csv|json
Cap5 000 rows (CollectionItemExportService::MAX_ROWS)
Shared withAI tool ExportCollection

Response headers: X-Export-Rows, X-Export-Total-Matched, X-Export-Truncated.

Item History (revisions)

From the item form, History opens:

ActionRoutePermission
List / compareGET …/items/{item}/revisionscan-show-collections
RestorePOST …/items/{item}/revisions/{revision}/restorecan-edit-collections
  • Snapshots are stored in collection_item_revisions on every CollectionItemValuesWriter::sync (create / update / restore).
  • UI: select two revisions to diff field JSON; Restore writes the snapshot as a new update (and records another revision).
  • Soft-deleted items keep history.

Activity log (/activity-logs) remains separate — actor/event audit, not version store.

Unsaved changes & destructive confirms

Item create/edit (and singleton show) register with the admin leave guard:

  • Unsaved badge + Discard appear in the header toolbar while the form is dirty. Discard opens Keep editing / Discard changes, remounts the form with original values, and stays on the page.
  • Navigating away via sidebar/links while dirty shows the same dialog; Discard then continues navigation.
  • Delete on the item form (and History Restore) opens a confirm dialog — no bare submits or window.confirm.

List views (items index) confirm soft-delete, bulk delete, and permanent delete with a count when multiple rows are selected.

See also: Roles & permissions (role form Discard), Project settings (settings Save bar Discard).

Browser draft autosave

While editing an item, dirty field values are debounced into localStorage (externa:item-draft:{collectionId}:{itemId|new}). Reloading the form shows Restored unsaved draft with Discard draft (clears storage and remounts server values). Successful Save/Create clears the draft. This is browser-only — not a server draft status and not shared across devices.

Preview as role / public

On an existing item, Preview as… opens a dialog to pick any role (including public). GET /collections/{collection}/items/{item}/preview-as-role (collections.items.preview-as-role) runs the same ACL + file expand pipeline as the public CMS API and returns read-only JSON (redacted fields omitted; public files without Files → Read appear as { id, access: "denied" }). If the role lacks collection Read or the item fails the item filter, readable is false.

See Roles & permissions and Public CMS API — access: denied.

Activity strip

Below the item form header: last updated by/at, links to Revisions and Activity (filtered /activity-logs?subject_type=App\Models\CollectionItem&subject_id={id}), plus the last few Spatie activity rows for the item subject (row-level events — field diffs live in revisions).

Singleton upsert

ItemDetail
RoutePUT /collections/{collection}/singleton-content
Namecollections.singleton-content
RequestUpsertSingletonCollectionItemRequest
Guard404 unless $collection->is_singleton

Behavior:

  1. Create the singleton item if missing.
  2. Otherwise merge arrays field-wise, strip readonly fields, normalize, sync values.

Field options endpoint

ItemDetail
RouteGET /collections/{collection}/items/options
Namecollections.items.field-options
ServiceCollectionItemOptionsService::paginateForField

Query parameters: field_id (required), related_collection_id, search, page, per_page (1–100, default 20). Response pages of { id, label } for relation pickers.

Relational / M2M editing

Field familyUI / payload
Relation / ManyToOne / RelationTreeScalar related item id
RelationMany / OneToManyArray of ids
ManyToManyArray of { related_item_id, meta } (bare ints still accepted on write)
M2AArray of { related_collection_id, related_item_id }

Junction metadata (M2M)

Optional settings.junction_fields on the field (JSON mini-schema in field settings):

[{ "name": "sort", "type": "number" }]

Allowed types: string, number, boolean. The item form shows meta inputs per linked item. API consumers should send:

{ "related_item_id": 12, "meta": { "sort": 1 } }

Source map

ConcernLocation
Item controllerapp/Http/Controllers/Collections/ItemController.php
Revisionsapp/Http/Controllers/Collections/ItemRevisionController.php
Rule builderapp/Services/Collections/CollectionItemDataRuleBuilder.php
Query / filtersapp/Services/Collections/CollectionItemQueryService.php
Routesroutes/collections.php
Pagesresources/js/pages/collections/items/*
Previous
Collections