Skip to content

Layout Builder

This page is generated from public package documentation in capell-4/packages and the package manifest checked into the source repository.

FieldValue
Composer packagecapell-app/layout-builder
Package sluglayout-builder
Product groupCapell Foundation
Tierfree
Bundlefoundation
Runtime contextsadmin, frontend, console
Capell version^4.0
Source repositorycapell-app/packages
Source pathpackages/layout-builder
Docs sourcepackages/layout-builder/docs
Manifestcapell.json

capell-app/layout-builder owns Capell’s visual layout composition layer: layout containers, blocks, block assets, public layout graphs, content-first editing, and the Filament layout editor.

Core still owns sites, pages, languages, URLs, themes, and base content models. Admin still owns the Filament panel shell. Layout Builder plugs into both through package registrars and exposes its public API from the Capell\LayoutBuilder namespace.

  • Provides the visual composition layer for Capell: layouts, containers, blocks, assets, public render graphs, and editor mutations.
  • Helps editors assemble pages without storing theme-specific presentation markup in database content fields.
  • Gives developers Actions and registries for public-safe layout payloads, reusable presets, layout areas, and content-first editing.
Terminal window
composer require capell-app/layout-builder
php artisan capell:layout-builder-install

The install command publishes and runs the package migrations listed by Capell\LayoutBuilder\Support\CapellLayoutBuilderManager.

Configuration lives in config/capell-layout-builder.php.

KeyPurpose
editor_mode.defaultDefault editor mode. Defaults to content_first.
editor_mode.allowedAllowed modes. Current values are content_first and layout_first.
preview.match_frontend_container_layoutMatch admin preview container layout to frontend columns.
block.skip_render_emptySkip empty blocks in public rendering.
default_blockDefault renderable key for new blocks.
SurfacePackage path
Public graph buildingsrc/Actions/BuildPublicLayoutGraphAction.php
Public block payloadssrc/Contracts/PublicBlockPayloadContributor.php, src/Support/DefaultPublicBlockPayloadResolver.php
Layout areassrc/Support/LayoutAreas/LayoutAreaRegistry.php, src/Actions/ResolveLayoutAreaContainersAction.php
Block presentation projectionsrc/Actions/ResolveBlockPresentationDataAction.php
Layout health checkssrc/Actions/AnalyzeLayoutHealthAction.php
Reusable layout presetssrc/Models/LayoutPreset.php, src/Actions/SaveLayoutPresetAction.php, src/Actions/ApplyLayoutPresetAction.php
Content-first inventorysrc/Actions/BuildLayoutContentInventoryAction.php
Layout mutationssrc/Actions/Mutations/
Filament resources and schemassrc/Filament/
Livewire editorsrc/Livewire/Filament/LayoutBuilder.php
Admin views and componentsresources/views/

Use Capell\LayoutBuilder\Actions\BuildPublicLayoutGraphAction when a public route, API, or package needs layout content without depending on the frontend renderer.

$graph = BuildPublicLayoutGraphAction::run(
layout: $layout,
page: $page,
language: $language,
containers: ['main'],
includeHtml: false,
);

Payload contributors are tagged with Capell\LayoutBuilder\Contracts\PublicBlockPayloadContributor::TAG. Contributors should return public-safe data or HTML only; do not expose admin state, editor-only metadata, private IDs, or unpublished content.

Block variants and settings are stored as authoring state in block meta, but public rendering receives only the sanitized presentation payload:

[
'variant' => 'split-media',
'spacing' => 'normal',
'background' => 'default',
'mediaPosition' => 'top',
'cardsPerRow' => 3,
'showCta' => true,
'headingWidth' => 'normal',
'anchorId' => null,
]

Public Blade must not query the database, lazy-load relationships, resolve block contracts, expose raw meta, or include authoring selectors, signed URLs, diagnostics, package internals, schema, labels, or preview/admin view names.

Layout areas let a theme expose named places where normal Layout Builder blocks can render outside the main page-body loop. The storage model stays unchanged: blocks still live inside layout containers, and containers may set meta.area.

  • Missing meta.area is treated as main for backwards compatibility.
  • main is built in and rendered by the normal main-content hook.
  • Themes and packages can register extra areas through Capell\LayoutBuilder\Support\LayoutAreas\LayoutAreaRegistry.
  • The Foundation Theme registers header, so editors can place normal blocks into the site header without hidden containers or a separate data model.

Register areas from a package service provider after the registry resolves:

use Capell\LayoutBuilder\Support\LayoutAreas\LayoutAreaRegistry;
$this->app->afterResolving(
LayoutAreaRegistry::class,
function (LayoutAreaRegistry $registry): void {
$registry->register(
key: 'header',
label: __('capell-layout-builder::generic.header_area'),
);
},
);

If an area only applies to one active theme, pass the theme key:

$registry->register(
key: 'announcement',
label: __('capell-theme-client::layout_areas.announcement'),
themeKey: 'client',
);

Public area rendering should use the package renderer rather than querying from Blade:

<x-capell::layout.area area="header" />

The area component reads the already-resolved layout containers and uses the stored CapellLayoutManager block instances. Keep public Blade query-free and authoring-free; area keys are public placement data, but editor state, model IDs, field paths, signed URLs, and package/admin metadata must stay out of the HTML.

Apps and package seeders should use AttachBlockToLayoutAreaAction when placing blocks into a named area. The action creates the area container when needed, normalizes the area key, preserves existing container metadata, and avoids duplicate block/occurrence pairs.

use Capell\LayoutBuilder\Actions\AttachBlockToLayoutAreaAction;
AttachBlockToLayoutAreaAction::run(
layout: $layout,
area: 'header',
blockKey: 'announcement-bar',
containerKey: 'site-announcement',
containerMeta: ['container' => 'full'],
);

Saved agency presets are persisted in layout_presets and scoped to a required site_id with optional theme_key. Presets are layout-only by default: they deep-copy structure, selected block variants, and settings without duplicating client content. Applying a preset revalidates site scope and regenerates duplicate anchors.

The older in-session LayoutPresetRepository remains only for temporary editor fragments; package and agency presets should use SaveLayoutPresetAction and ApplyLayoutPresetAction.

Use capell:layout-builder-block-visual-regression capture or assert to emit deterministic block/variant/viewport fixture entries for a screenshot runner. The command supports --block, --theme, --variant, --changed, --concurrency, and --ci-limit.

The command does not authenticate, generate signed routes, query tenant content, or use live media. Browser capture/compare remains the responsibility of the runner.

content_first is the default mode. It shows editor-facing content groups from the current layout state and lets editors update assigned block assets without navigating the full layout canvas.

layout_first opens the drag/drop layout builder directly. Keep it available for designers and implementers who need placement and structure control.

Both modes write through the same BlockAsset persistence path.

Run the package suite from the packages monorepo:

Terminal window
vendor/bin/pest packages/layout-builder/tests --configuration=phpunit.xml

Run the focused public graph and package-boundary checks after changing public rendering or package ownership:

Terminal window
vendor/bin/pest packages/layout-builder/tests/Integration/PublicLayoutGraphActionTest.php packages/layout-builder/tests/Arch/LayoutBuilderPackageBoundaryTest.php --configuration=phpunit.xml