A shared row is a row saved once and reused on multiple pages. Editing it on any page updates every page that uses it — so site-wide CTAs, footer variants, contact blocks, and other repeated sections only have to be maintained in one place. Shared rows are managed from the Dashboard → Shared Rows page and created from the page editor by toggling any row to "Shared".
The problem
Every site has sections that need to appear on multiple pages with identical content: a "Book a demo" CTA, a small contact block, a promotional banner, a customised footer variant. Without a sharing mechanism, the editor either has to duplicate the row on every page (and remember to update each copy when the content changes) or wire the section into the layout (which makes it global and removes per-page placement control).
Shared rows give editors a third option: place a row anywhere on any page, but keep its content centralised so an edit propagates everywhere automatically.
The fix
Any row on any page can be turned into a shared row from the page editor — the row's contents move into a dedicated views/shared-rows/{slug}.blade.php file, and the original page now references the row via @include instead of holding its content inline. Adding the same shared row to another page reuses the same @include, so both pages render the same source.
Editing a shared row from any page that uses it writes back to the shared file. The next page render — anywhere the row is included — picks up the change.
Creating a shared row
From the page editor, every row card has a "Make shared" action. Clicking it:
- Writes the row's blade to
resources/views/shared-rows/{slug}.blade.php(the slug uses dashes instead of colons to be filename-safe) - Replaces the row's inline blade in the page file with
@include('shared-rows.{filename}') - Inserts a
shared_rowsrow to register the slug + display name - Updates existing
ContentOverriderows for the slug, settingpage_slug = nullso the overrides are page-agnostic going forward
The row's editor card now shows a "Shared" badge and the field values become the source of truth for every page that includes it. Implementation lives in EditorLibraryActions::makeRowShared.
Adding a shared row to another page
The page editor's "Add row" drawer has a Shared tab listing every shared row on the install. Clicking one inserts an @include('shared-rows.{filename}') line into the target page at the chosen position. No content is copied — the new page renders the same shared file directly.
Editing a shared row
A shared row appears in the editor exactly like any other row — the same item cards, the same Content / Design / Advanced tabs, the same field types. Its content (text, images, styling) is shared: an edit saves to the global record every page reads, so every page that includes the row shows it on its next render.
Changing a shared row's layout
A shared row's layout — adding, removing, duplicating, moving, renaming or hiding items, looping an item, a repeater's fields, a grid's layout — can be changed from any page that uses it, and from the Shared Rows page's editor. Saving writes the change to the shared file, so it reaches every page that uses the row.
It asks first. A shared row's items open locked, with a notice — "Shared row — changes affect all pages using it" — and an Edit layout for all N pages button. Clicking it unlocks the layout for the rest of this editing session (a reload asks again). Trying a layout change before that — from the sidebar, the canvas's right-click menu, a keyboard shortcut or a drag — opens Change this shared row's layout?, which lists the pages that use the row: Edit layout for all N pages unlocks and applies the change, Cancel leaves the layout as it was, and Detach instead… opens the Detach confirmation below. A row used on only one page skips the question but still says it is shared.
N counts every live file that renders the row: pages at any depth, the site header / footer, popups and the pages of enabled feature modules — the same number the Shared Rows page shows.
Who can. Managers and up — the same as editing the row's content. PHP and Livewire items stay Admin-only, inside a shared row as anywhere else. The system rows (Page Title, Call to Action) follow the same rule as any other shared row.
Where it stays locked, with a notice saying why:
- a row customized for this page only — its content is per-page, but its layout is still shared (detach it for a layout of its own);
- a pasted copy of a shared row on another page (it still points at the original's file);
- shared rows inside the header / footer editor and popups, and theme workspaces.
A shared row's design isn't swapped from a page: the section settings' Design Library browser isn't offered on it. Switch designs on the Shared Rows page, re-pull its own template with Refresh from design library (Reset / Refresh), or detach it first.
Detach into a separate row for this page… turns the row into an ordinary row on this page with the same content and the current layout (including an unsaved layout change), so it can change freely without touching other pages; the other pages keep the shared row. It is offered from the lock notice, the warning above, the row's ⋯ menu and the Design tab's shared-row panel, works on the home page too, and nothing changes until you save.
Copy and paste. Items can be copied out of a shared row and pasted anywhere. Pasting items into a shared row changes its layout, so it asks first while the row is locked. Cut — and moving items into or out of a shared row — is refused ("Items can't be cut out of a shared row — copy them instead."), because a cut would remove them from every page using the row.
Undo steps back through layout changes like any other edit, even past a save. An undo never reverts another editor's save: if the shared row was saved elsewhere since, the undone layout is refused and the current one is shown.
Two editors at once. If two people change the same shared row's layout, the second save merges the two when they touched different items; when they changed the same item, the save asks which version to keep ("You and another editor both changed the layout of this shared section. It appears on other pages too."). A layout change made on a version that has since been saved elsewhere is not kept — "This shared section changed while you were editing, so your layout change was not kept. The latest version is now shown." — and edits typed into an item another editor removed are dropped with "Some edits were not saved — their items were removed from a shared section by another editor."
Restore an earlier layout… (the row's ⋯ menu, also in the Shared Rows page's editor). Every save that changes a shared row's layout — and every refresh from the design library — keeps the layout it replaced, up to the 30 most recent per row. Restoring one stages it in the editor (undoable); it reaches every page when you save.
Unsaved layout changes are not lost. A reload brings them back with the other recovered changes ("Shared section layout changed — row name" in the recovered-changes banner), and another editor can import them (Draft Import).
Switching it off. CMS_EDITOR_SHARED_ROW_LAYOUT_EDITING=false (config cms.editor.shared_row_layout_editing, on by default) locks every shared row's layout again — content stays editable, and the lock notice says why.
Refreshing a shared row from the design library
If a shared row's source design library template has been updated (e.g. a designer pushed new defaults), the shared row can be refreshed from that template. The page editor's per-row refresh action handles shared rows by writing the new template to the shared-rows/{slug}.blade.php file and re-baking active overrides on top.
The page-wide refresh action skips shared rows by default because refreshing one shared row affects every page that includes it. An opt-in checkbox in the refresh modal — "Include shared rows on this page" — lets the user choose to refresh them anyway. Implementation lives in EditorDiscardActions::refreshSharedRowBlade.
Every refresh keeps the file it replaces as an earlier version first — the editor's refresh and the Shared Rows page's Refresh alike (SharedRowVersions) — so a customised layout a refresh threw away can be brought back from the row's ⋯ menu → Restore an earlier layout…. When this editing session holds an unsaved layout change to a shared row, both the per-row and the page-wide refresh confirmations warn that the refresh replaces it.
The Shared Rows dashboard
Dashboard → Shared Rows lists every shared row in the install with:
- The display name and slug
- The number of pages using it — every live file that renders the slot's active design (pages at any depth, the header / footer, popups, enabled feature pages), counted by
SharedRowUsage, the same counter behind the editor's "Edit layout for all N pages" - An Edit button that opens the editor — either on the first page that uses the row, or in a standalone wrapper file if no page currently includes it
- A Delete button that removes the row
Deleting a shared row scans every page file for ROW:start:{slug} markers, removes the include line from each affected page, and deletes the shared file plus its shared_rows registry row. The delete confirmation modal lists every page about to be affected so the user sees the blast radius before confirming. Implementation lives in ⚡shared-rows.blade.php.
Override storage and propagation
Field values for a shared row are stored as ContentOverride rows with row_slug = {shared_slug} and page_slug = null — i.e. not scoped to any one page. When any page renders the shared @include, the row hits the same set of overrides, so all pages render the same content.
This is how propagation works without any push step: every page reads from the same DB rows. Editing those rows from one page is identical to editing them from any other page; the next render anywhere reflects the new value.
If a single page needs to diverge on a few fields without losing sync on the rest, use per-page customization. To split a row into entirely separate variants, convert it back to a regular row (or insert a fresh non-shared row from the design library).
Per-page customization
A shared row can be customized for one specific page while keeping every other page in sync with the global content. This is useful when a single landing page needs a different headline or CTA label than the rest of the site, but everything else about the row should still propagate.
In the page editor, a shared row's card shows a paint-brush "Customize for this page only" button alongside its share badge. Clicking it puts that row into per-page override mode on the current page only:
- The badge changes to an amber "Page only" label so it's obvious at a glance that this instance has diverged from the global shared content.
- Every field on the row — text, headings, toggles, images, repeaters, and
*_classesstyle fields — saves to a per-page override record instead of the global one. - Other pages that include the same shared row are unaffected and continue to read the global values.
Editing the row globally from a different page still works the same way. A page-specific override on Page A always wins for Page A; everywhere else falls back to the global record. Likewise, fields the user did not touch in page-override mode continue to inherit from the global record — only the specific keys the user set on Page A diverge.
A "Reset to shared" action sits next to the page-only badge. Clicking it deletes every per-page override for that row on the current page and clears any per-page class overrides written into the page file, returning the row to its global shared values.
Class fields (anything ending in _classes) are written into a comment block above the row's @include line in the page file so Tailwind's scanner can see them at build time. Resetting clears that block automatically.
When the editor opens a page that already has per-page overrides for a shared row, the row starts in page-override mode automatically — no need to click the customize button again.
A row customized for this page only can't change its layout — that is still shared. Its sidebar says so and offers Detach into a separate row for this page… instead. Switching a row to page-only while this session holds an unsaved layout change to it is refused ("Save or undo this row's layout change first…").
Importing another editor's unsaved changes (Draft Import) can also put a row into page-override mode: when they had customized it for this page and you hadn't changed it, the import switches your copy to page-only so their changes stay on this page. Undoing the import leaves the row page-only.
What it isn't
- Shared rows are not partial templates. They render as full editable rows with their own override store. A partial template that needs PHP logic or layout-level placement is still a Blade view in
resources/views/. - Shared rows don't override the layout's header or footer slots. Site-wide header and footer are managed through the layout / branding system, not the shared rows feature. Shared rows live inside the page body.
Common use cases
| Pattern | Why share it |
|---|---|
| Site-wide "Book a demo" CTA | Update the headline or button text once, reflect it across every landing page |
| Footer variants | When the footer differs between marketing pages and product pages, share each variant separately |
| Newsletter signup | Centralise a single signup form so subscriber-list changes only happen in one place |
| Contact block (address, phone, hours) | Treat business info as a single source of truth |
| Promotional banner | Roll out a campaign banner across the site, then turn it off in one place when the campaign ends |
| Standardised testimonials section | One canonical testimonials grid reused across product pages |
What lives where
| Path | Purpose |
|---|---|
app/Models/SharedRow.php |
Eloquent model — slug + name registry of every shared row in the install. |
resources/views/shared-rows/ |
The shared blade files. One per shared row, named {slug-with-dashes}.blade.php. |
resources/views/pages/dashboard/⚡shared-rows.blade.php |
The Shared Rows dashboard — list, edit, delete with usage counts. |
app/Support/VoltFileService.php |
makeRowShared, deleteSharedRow, file I/O for shared row blade files. |
app/Concerns/EditorLibraryActions.php |
makeRowShared (convert row to shared), insertSharedRow (add a shared row to a page). |
app/Concerns/EditorDiscardActions.php |
refreshSharedRowBlade and the page-wide opt-in flag for refreshing shared rows. |
app/Support/Editor/SharedRowFiles.php |
Shared-file paths, include-target resolution, the hidden-item read-unwrap / write-wrap, and the client-tree sanitizer for layout edits. |
app/Support/SharedRowUsage.php |
The one "used on N pages" counter. |
app/Support/Editor/SharedRowVersions.php · app/Concerns/EditorSharedLayoutVersionActions.php |
Earlier layouts (shared_row_versions) and the editor's Restore an earlier layout. |
For the broader page editor that hosts the make-shared and add-shared actions, see page-editor-tour.md. For the catalogue of pre-designed rows that shared rows are typically created from, see design-library.md. For engineers touching the orchestration (cache tiers, save flow, override resolution), see shared-row-architecture.md.