# Collections

Source: https://docs.qiick.io/externa/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/`.

<!-- screenshot omitted -->

> **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](https://docs.qiick.io/externa/permissions), [Collections API](https://docs.qiick.io/externa/collections-api). Data shape: [Collections data model](https://docs.qiick.io/externa/collections-data-model). Field types: [Field types](https://docs.qiick.io/externa/field-types). Schema changes: [Schema changes](https://docs.qiick.io/externa/schema-changes).

## 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](https://docs.qiick.io/externa/field-types#collection-packs)                                                                                                            |
| 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`): `slug` optional; empty → `Str::slug(name)`; uniqueness via `UniqueCollectionSlugGenerator`.
- 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](https://docs.qiick.io/externa/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](https://docs.qiick.io/externa/field-types#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/*`                     |
