Every WebProCMS shared row is a named slot with one active design: edit the design and every page that uses the row updates; swap in a different design and every page that follows the row switches at once. The system ships two slots — Page Title and Call to Action — and you can create your own ("Testimonial Band," "Holiday Banner") by sharing any row from the page editor. One concept, one flat list, no tabs.
A slot always resolves to one concrete instance (a real, editable row), but pages reference the slot, not the instance — so the design can change underneath them without breaking anything. It's the same idea as the default Header and Footer (a page says "use the site header," not "use header #3"), extended to any band you want reused across pages.
What it gives you
- A site-wide default page title and CTA. Generated content-type pages (services, projects, etc.), listing pages, and detail pages all lead with your chosen page-title band and — where appropriate — close with your chosen CTA, without you placing them by hand.
- Your own reusable rows. Style a band once, share it, and insert it anywhere. Editing it updates every page that uses it.
- One-click design swaps that reach every page. Each shared row can hold several designs; make a different one active and every page following the row switches to it. The seasonal-banner swap (holiday design in December, normal design in January) is one click each way.
- User-authored designs for the system slots. Style a page-title band on any page, then "Add to existing → Page Title" in the share modal — your design joins the Page Title library beside the shipped ones, swappable like any other.
- Per-page freedom when you want it. A page can pin a specific design instead of following the active one, or customize the shared row for that page only. Following the active design is the convenient norm, not a cage.
Late binding: the heart of the feature
When a page uses a shared row, it doesn't bake in a copy — it stores a reference to the slot. The actual band is resolved when the page renders. So:
- Changing the active design updates every page that follows it — instantly, with no rebuild and no per-page edits.
- It can't break a page. If a slot ever can't be resolved, the row simply renders empty rather than erroring — pages stay up no matter what.
- It's free at render time. Resolving the slot is an in-memory settings lookup the page already pays for; it adds no database queries, and fully-cached pages are unaffected.
Where you manage it
Dashboard → Design → Shared Rows is one flat list: the system Page Title and Call to Action slots first (marked System), then your own shared rows. Each entry shows a live preview of its active design, how many pages use it, and:
- Edit — opens the active design in the row editor.
- Designs — the slot's detail view: installed designs with an Active badge, Make Active on the others, plus Edit, Refresh (re-pull structure from the design library), and Delete (with a usage warning; the active design can't be deleted — make another active first). System slots also get a Library pill listing shipped designs to Install & Use or Install only.
- Delete (user rows only) — removes the shared row everywhere, with a list of affected pages first. System slots can't be deleted.
New shared rows are created from the page editor, not from this page — see below.
Sharing a row from the page editor
Click the share icon on any row and the Share this row modal offers two paths:
- New shared row — name it, and on save it becomes a new slot with this row as its active design. The page follows the slot from then on.
- Add to existing — pick a shared row (Page Title, CTA, or one of yours), name the design, and choose:
- Make it the active design off (default): the design joins that row's library and this page is pinned to it. No other page changes.
- Make it the active design on: it becomes the row's active design — the modal shows how many pages will update before you commit.
Nothing is written until you save the page; undo (or leaving without saving) cancels the share cleanly.
In the editor's Add Section drawer, the Shared Rows tab lists every slot with its active design — inserting one drops a late-bound reference, not a frozen copy, so the page follows the row from then on.
Moving pages between sites
- Cloning a page (same install) keeps references — the clone follows the same shared rows.
- Exporting a page bundle (the
.zipyou can import into another WebProCMS site) carries the system Page Title / CTA as a reference: import it and the page adopts that site's page title and CTA. A user-created shared row (or a pinned design) is carried as a portable copy instead — the destination doesn't have your slot registry, so the row arrives as an ordinary local row with its content intact.
How it works (technical)
- Slot registry —
App\Support\SharedRowRoles. The two system slots (page_title,cta) live in theROLESconst with their label, template-name families, library criteria, and theme-shipped default. User slots live in theshared_rows.custom_slotsSetting ({key: {label}}), created viacreateSlot()(key derived from the label, e.g.holiday_banner) and removed viaforgetSlot().roles()merges both;isSystemRole()distinguishes them. - Instances are shared rows with explicit membership. A slot resolves to a concrete
shared-rows/{template}-{id}.blade.php+ aSharedRowrecord. Theshared_rows.rolecolumn stamps which slot an instance belongs to (shared-rows:sync-rolesbackfills legacy records;familyInstances()keeps a template-prefix fallback for unstamped system rows). The install's active design per slot is a Setting (shared_rows.role.{key}). - Dashboard operations —
App\Support\SharedRowRoleService: install / setDefault / refresh / usage / uninstall, plus the v2 additionsadoptInstance()(stamp a shared page row into a slot, optionally swapping the active design site-wide),createSlotFromInstance()(the share modal's "new" path),roleUsage()(pages following a slot late-bound), anddeleteSlot()(user slots: strips the row from every page, removes instances + registry). - The share modal — the row card's share button opens a modal (on
EditorModals) that stages{mode, name, role, make_default}via the editor'sstageRowShare(). Configs are held server-side in$pendingShareConfigskeyed by row slug (the row'spending_sharedflag is boolean-coerced through the client round-trip, so the config can't live on the row).saveFile's materialization loop writes the shared file, then creates/joins the slot and rewrites the page row as a late-bound reference (new slot or make-default) or a pinned concrete embed (add-without-default). - Late binding is a Blade directive. Pages store a row marked
{{-- ROW:start:role:{key}:shared=1 --}}whose body is@sharedRole('{key}'). The directive resolves the slot to the install's active instance at render and@includes it, sharing the page's data ($pageName, breadcrumb context). Resolution is read-only (no DB writes on the render path) and null-safe (renders empty if unresolved — including for a deleted custom slot). The page-data compiler indexes the resolved instance's content into each page's sidecar, so the band's copy renders with no extra queries. All of this is generic over the slot key — user slots ride the same markers, directive, compiler scan, and fan-out recompile as the system slots. - Edit-in-place. When the editor loads a page, a
role:*marker is resolved to its concrete instance so the editor treats it like a normal shared row (content overrides, drill-in, preview, per-page override mode). On save the role marker is restored — so opening and saving a page never silently converts it to a hardcoded include. - Migration.
php artisan pages:migrate-shared-rolesconverts pre-role pages' hardcoded page-title/CTA includes to role markers (only where the row matches the slot's current instance; idempotent). It runs automatically during CMS updates, aftershared-rows:sync-roles(which also stamps therolecolumn).
Adding a new system slot
Add an entry to SharedRowRoles::ROLES with its label, families, library criteria, and default instance. The dashboard list, the editor's Shared Rows drawer tab, the share modal's slot picker, late-binding resolution, the compiler scan, and the migration command all derive from the registry — no per-slot wiring elsewhere.