Skip to content

Optional Package Boundaries

Composer availability is not Capell extension availability. A package class can autoload while the extension is not installed, disabled, or missing its tables.

Do not use class_exists() as the only guard before calling optional Capell package runtime code:

if (class_exists(Navigation::class)) {
Navigation::query()->first();
}

Use Capell’s installed-state check first:

if (CapellCore::isPackageInstalled('capell-app/navigation') && class_exists(Navigation::class)) {
Navigation::query()->first();
}

If the code can run during install, upgrade, diagnostics, or another partial database state, also guard the table before querying:

if (
CapellCore::isPackageInstalled('capell-app/navigation')
&& Schema::hasTable('navigations')
&& class_exists(Navigation::class)
) {
Navigation::query()->first();
}

This applies to models, Actions, Blade components, Filament fields, listeners, render hooks, and service-provider registrations that touch package runtime behavior.

class_exists() is still appropriate for non-Capell PHP/library capabilities, dynamic configured classes, autoload priming before cache deserialization, and defensive validation after the Capell package has already been proven installed.

Optional packages must not make capell/frontend import their models or Actions to decide public runtime requirements. Use Capell\Frontend\Contracts\FrontendRuntimeManifestContributor instead.

Example: layout-builder registers LayoutBuilderRuntimeManifestContributor during package registration. The contributor inspects layout-builder element data and adds the layout-builder, Alpine, Livewire, and island flags to FrontendRuntimeManifestData. Frontend only loops over tagged contributors and remains installable without layout-builder.

Theme or frontend packages that need editor-managed content in page chrome should use Layout Builder areas instead of hidden containers, render-hook side effects, or package-owned placement tables.

Register named areas through Capell\LayoutBuilder\Support\LayoutAreas\LayoutAreaRegistry only when capell-app/layout-builder is an installed dependency for that package path. The built-in main area is always available. Missing container meta.area values are treated as main, so older layouts continue to render in the normal content loop.

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 the area belongs to one active theme, pass the theme key so the editor select only shows the area for that theme:

$registry->register(
key: 'product-nav',
label: __('capell-product::layout_areas.product_nav'),
themeKey: 'product',
);

Public rendering should use the theme area component:

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

Do not query layout containers, elements, pages, media, or package models from public Blade to render an area. Build or preload the public render data before the view layer, and keep editor-only metadata out of anonymous HTML and static caches.