orchestra/extension
Orchestra Extension adds modular extension support to Laravel, letting you package features as self-contained modules with manifests, service providers, and activation controls. Manage, activate, and migrate extensions through a central API for cleaner, modular applications.
Architecture fit. orchestra/extension is a horizontal lifecycle layer, not a domain package. What it actually delivers is a registry with four primitives — detect, activate, deactivate, and dependency-ordered boot — plus orchestration of per-extension migrations and asset publishing. That is a real, non-trivial capability and it is exactly what makes a modular monolith viable without a redeploy per feature. It is also the entire scope. There is no sandboxing, no versioned host→extension API contract, and no signed registry, so the word "extension" here means "managed Composer package with a manifest," not "contained plugin." Any roadmap that assumes third-party code safety must scope that as separate work.
Integration feasibility. The feasibility question splits cleanly in two. Inside Orchestra Platform (i.e., orchestra/foundation already installed), this package is present transitively and the decision is really "pin and own deliberately" — cheap. On vanilla standalone Laravel you can wire it up, but you are hand-reproducing Orchestra's conventions (config/orchestra/extension.php, the path scan root, the orchestra.json manifest schema) and absorbing all the coupling cost for a capability you could approximate. The gating check is a single Composer dry-run against the target PHP and illuminate/* versions; if that fails, nothing downstream matters. Last visible milestone is Laravel 8, so on Laravel 9+ the honest framing is a fork, not an adoption.
Technical risk. Several concrete, well-understood risks rather than speculative ones:
config:cache can freeze extension configs at build time. Both are deploy-time landmines that surface as environment-specific bugs.Factory contract early — retrofitting after extensions exist against global state is expensive.Key questions to resolve before committing. Which Laravel version does the target app actually run, and is orchestra/foundation already present? Is runtime lifecycle control a genuine product requirement, or is this architecture aesthetics — i.e., would a plain Composer package with a service provider solve the same problem? Is per-tenant activation in scope? Will any code in an extension ever come from outside the team, now or on the roadmap? Who owns the fork, and what is the upgrade cadence? Which mechanism owns feature gating — config flags, installed package state, or an admin-controlled activation record? Those three produce very different support postures.
Stack fit. Good inside Orchestra Platform on Laravel 8: near-zero integration work, conventions already established, provider registration conditional on activation for free. Poor on modern standalone Laravel: the value is capped by the coupling, and the adjacent stack — nwidart/laravel-modules for scaffolding, Laravel's native package discovery for registration, and a small application-owned enabled_extensions table — covers roughly 80% of the benefit with a clean ownership story.
Migration path. Two viable routes and one non-route.
migrate --force and publish steps in the deploy script. Correct for greenfield on Laravel 9+.Compatibility work is bounded and testable. Verify cold-cache behaviour explicitly: run config:cache and route:cache in a staging environment with an extension both enabled and disabled, and confirm the enabled surface appears and disappears. Confirm multi-node cache invalidation, and confirm autoload after composer install --no-dev. These three checks de-risk most of the operational list below.
Sequencing. Run the Composer dry-run first — one hour, gates everything. Then a one-day spike: a single hello-world extension through detect → activate → migrate → publish with a logging provider. That loop is the whole value proposition; if it works, the rest is repetition, and if it fails you have spent a day rather than a quarter. Only then write the decision record, and only then decide fork-versus-replace. If adopting: fork and vendor, wrap the Factory in an application-owned ExtensionManager (enable/disable/sync) so CLI and any admin surface share one code path, encode deploy ordering as a script (composer install → extension:detect → extension:migrate → extension:publish → cache build), and if per-tenant activation is required, bind the custom Factory implementation before authoring extensions.
Maintenance. Treat this as internally owned headcount, not a dependency. There is no visible release cadence to ride and no upstream security process to inherit; the fork is ours to patch. The compensating discipline is treating activation state as schema — versioned, shipped through deploys, reviewed like a migration, never hand-toggled in production. That single rule prevents most of the failure modes below and is worth stating in the decision record.
Support. Expect a recurring ticket class of "extension is active but broken," which is nearly always autoload drift, stale discovery cache, or a partial activation. Mitigate with an explicit post-activation verification step in the deploy path — assert a migration row exists, assert a published asset is present, fail loudly. If customer administrators or internal ops are permitted to toggle modules themselves, the support surface expands into customer-induced states; that is a real operational cost and should be priced into the on-prem or licensed-module offering rather than absorbed silently.
Scaling. Discovery is cached, so steady-state cost is low; the scaling risk is consistency, not throughput. On multi-node deployments one node can serve a different module view than another until caches clear, so invalidation becomes a release-checklist item — ideally automated, not assumed. Per-tenant activation multiplies both state and cache surface and should be modeled as a distinct scaling dimension before it is built.
Failure modes to plan for. Activation/autoload mismatch; stale discovery cache after deploy; config:cache freezing extension configs read at build time (mitigate by merging configs inside the extension provider so they resolve at boot); silent partial activation; dependency-order violations from manifests that omit a require entry; test flake from global activation state leaking between tests — activate explicitly in setUp() and isolate disabled-state tests in their own class.
Ramp-up. A competent Laravel engineer becomes productive in roughly a week, but the durable learning is operational rather than API-level: manifest-first discipline (lifecycle metadata in orchestra.json, code metadata in composer.json, kept in sync), lifecycle semantics, and deploy ordering. Upstream documentation assumes Orchestra context, so onboarding material is ours to write. Budget for that, and budget for the fact that the first engineer to leave takes the tribal knowledge unless the deploy script and runbook are written down early.
How can I help you explore Laravel packages today?