Any individual content-type item — a single service, one portfolio piece, a specific project — can be promoted to its own standalone, fully-editable detail page with arbitrary design-library rows, while every other item of the type keeps rendering through the shared template. The promoted page still reads the item's structured fields live ({item.auto.title}, {item.data.description}, {item.featured_image}, etc.), so you get the convenience of structured data and the freedom of the full page builder on the one page that needs it.
The problem
Custom content types render every item of a type through one shared detail template ({type}/{record}). That's exactly right for a catalog where every record should look identical — but it falls down the moment one item needs more. A flagship service wants a comparison table, a testimonial strip, an FAQ, and three call-to-action bands; the other eight services are a paragraph and a photo. Forcing the flagship into the shared template means either bloating every item's template with rows that are empty 90% of the time, or giving up and hand-coding a one-off page that no longer shares the type's data.
The fix
Editing an item, open the actions menu and click Customize this page. The CMS materializes a real page — a blade file under resources/views/pages/{type}/{item-slug}.blade.php plus a route registered above the type's wildcard {type}/{record} route so it wins for that one slug — seeded from the type's current detail template (or the item's chosen layout variant), and drops you straight into the page editor. From there it's an ordinary editable page: add, reorder, restyle, and delete rows freely.
Two things make this more than "just clone the template":
- The fields stay live-bound. The page's
mount()binds the record by its database id (stable across slug renames), and the@dataBinddirective keeps{item.*}tokens resolving against it. Edit the service's name or description back in Dashboard → (type) → Edit and the custom page updates too — you're never maintaining the same copy in two places. - It's opt-in and reversible. Items you never customize keep using the shared template. Revert to default layout deletes the custom page (file, route, and its edits) and the item falls straight back to the shared template — no orphaned routes, no broken URLs.
How the three flexibility tiers stack
Custom item pages are the top rung of a ladder — each tier is opt-in and builds on the last:
- Rich text — the item's body field.
- Content blocks — drop CTA / gallery / FAQ blocks into the body via
[[type:slug]]shortcodes, without leaving the edit form (see Shortcodes). - Detail layout variants — pick a prebuilt full-page design for the item from a dropdown.
- Custom item page — break out into a bespoke, fully-editable page.
Tiers 3 and 4 coexist at the type level but are mutually exclusive per item: across a type, item A can use layout variant Wide, item B variant Narrow, and item C its own custom page. Because a promoted item is served by its own route, it never runs through the layout gate — so the layout dropdown hides once an item is promoted (it's superseded), and re-appears if you revert.
How an editor uses it
- Open a content item for editing (e.g. Services → Web Design).
- Open the Update split-button menu → Customize this page. (Shown only when the type allows it and has a public detail page.)
- You land in the page editor on the new page, pre-filled with the item's normal layout. Add rows, edit copy, restyle.
- Save. The public URL (
/{type}/{item-slug}) now serves your bespoke page. - To undo, return to the item's edit screen → Update menu → Revert to default layout → confirm. The item rejoins the shared template.
After promotion the menu shows Edit custom page (jumps back into the editor) and Revert to default layout instead of Customize this page.
Per-type control
Each content type has an Allow custom item pages switch (Dashboard → Content Types → edit, default on). Turn it off for high-volume catalog types where bespoke per-item pages make no sense (e.g. hundreds of meeting minutes) and the Customize this page action disappears for that type's items.
Lifecycle & safety
- Slug rename. Renaming a promoted item moves its page file, re-points its route, and migrates its edits to the new URL. Because the page binds by id, nothing breaks; the old URL 301-redirects to the new one (when a redirect is requested) through the wildcard route's previous-slug handling.
- Item delete. Deleting a promoted item tears down its page, route, edits, and compiled sidecar automatically.
- Type delete. Removing a whole content type sweeps every promoted item's page, route, and edits for that type.
- Drafts. A promoted draft item renders for signed-in dashboard users (so the editor preview works) but 404s for the public — exactly like an unpublished item on the shared route.
What lives where
| Path | Purpose |
|---|---|
app/Support/ItemPageMaterializer.php |
Promote / revert / rename / type-sweep. Built generically against a per-type descriptor (enabled for content types today; blog/events/locations can be added later). Reuses the page-clone plumbing in VoltFileService. |
app/Models/ContentItem.php |
has_custom_page flag (source of truth); a deleting hook reverts a promoted item. |
app/Models/ContentTypeDefinition.php |
allow_custom_item_pages toggle gating the feature per type. |
resources/views/pages/dashboard/content/⚡edit.blade.php |
The Customize / Edit / Revert actions + slug-rename wiring. |
app/Support/ContentTypePageGenerator.php |
remove() sweeps promoted pages before deleting a type. |