Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Extension Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Preconditions. The realistic entry point is an application already running orchestra/foundation; in that case orchestra/extension, orchestra/contracts, orchestra/memory, and orchestra/support are present transitively and the question is "pin and own" rather than "adopt." On vanilla Laravel you can wire it up, but you are re-implementing Orchestra's conventions by hand. Validate before writing a line of feature code:

composer require orchestra/extension --dry-run
php artisan list extension   # confirm command signatures on your tag

If the dry-run trips on PHP or illuminate/* constraints, stop — the last visible release note is Laravel 8 support.

Where to look first. Three files carry almost all of the operational knowledge:

  1. config/orchestra/extension.php — publish it and read the path, humanizer, and bootable keys. path (conventionally base_path('extensions')) is the directory the manager scans.
  2. Orchestra\Contracts\Extension\Factory — the runtime API you actually call: detect, activated, activate, deactivate, all. Everything else is plumbing around this interface.
  3. The orchestra.json manifest schema — name, version, author, and a require block that drives dependency-aware activation ordering.

First use case — prove the lifecycle end to end. Scaffold a trivial extension:

extensions/acme/hello/
├── composer.json          # name: acme/hello, PSR-4 autoload
├── orchestra.json         # name, version, description, require: {}
├── src/HelloServiceProvider.php
├── configs/  routes/  resources/views/  migrations/  resources/lang/

Give the provider a boot() that only logs, then walk the loop:

php artisan extension:detect        # scans path, builds the registry
php artisan extension:activate acme/hello
php artisan extension:migrate       # per-extension migrations
php artisan extension:publish       # assets + config

When the log line appears you have proved detection, activation, provider registration, and migration orchestration. That loop is the value proposition — everything after it is repetition. If it fails, you've spent a day, not a quarter.

Implementation Patterns

Manifest-first, Composer-second. Treat orchestra.json as the source of truth for lifecycle metadata (identity, version, dependencies) and composer.json for code metadata (autoload, package deps). Drift between the manifest's require block and what is actually in vendor/ is the most common "active but broken" state. If two extensions share a base library, declare it in the manifest rather than relying on install order — that is what buys you deterministic boot ordering.

One provider per extension, registered only when active. The manager registers extension providers conditionally; lean on that so deactivation genuinely removes surface area. Do not register extension routes from the host's RouteServiceProvider — you silently lose the on/off semantics.

// extensions/acme/hello/src/HelloServiceProvider.php
public function boot()
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'acme-hello');
    $this->loadMigrationsFrom(__DIR__.'/../migrations');
    $this->mergeConfigFrom(__DIR__.'/../configs/hello.php', 'acme.hello');
}

This is also the shape of your day-to-day edit cycle: change a route or view inside the extension, no host rebuild, no new package tag.

Gate host behaviour via the factory, not file_exists(). Activation state is your feature flag:

$extensions = app(\Orchestra\Contracts\Extension\Factory::class);

if ($extensions->activated('acme/hello')) {
    // register menu item, dashboard widget, nav entry
}

The consequence is important: activation state is now a runtime dependency of your UI, which is why it must ship through deploys rather than be clicked in production. Treat it as schema.

Wrap the factory in an application-owned manager. A thin ExtensionManager with enable(string $id), disable(string $id), and sync() — called by both Artisan commands and any admin toggle controller — gives you one place for logging, authorization, post-activation verification, and cache invalidation. You will need all four, and you do not want them duplicated.

Chain the lifecycle in enable(). Developers forget publish. Make enable() do activate → migrate → publish → verify, and throw if any step leaves the extension half-applied.

Encode deploy order as a script, not tribal knowledge:

composer install --no-dev
php artisan extension:detect
php artisan extension:migrate
php artisan extension:publish
php artisan config:cache && php artisan route:cache

detect before migrate, and clear the extension cache after composer install — otherwise a freshly installed extension stays invisible until someone clears it by hand in production.

Per-tenant activation, if that is the goal. The stock factory persists a single global activation set. To scope it per tenant, bind your own implementation of the Factory contract and filter activated() by the current tenant. Do this early and deliberately; retrofitting it onto extensions written against global state is painful.

Testing pattern. Activate the extension under test in setUp() through the factory rather than trusting whatever the local database holds, and keep tests that assume an extension is disabled in their own class.

Gotchas and Tips

Run the Composer dry-run before anything else. Stale illuminate/* constraints and a PHP ceiling are the likeliest first failure and cost an hour to discover.

"Active but not autoloaded" is the canonical bug. The activation record says yes; vendor/composer/autoload_*.php says no — usually a missing composer dump-autoload or a namespace never added to the autoload map. Symptom: class-not-found inside a provider the manager insists is active. Check autoload before you check the manager.

Cache staleness after deploy. Discovery results are cached. A new extension shipped in a release does not appear until the cache clears, and on multi-node setups one node can serve a different view of the world than another. Make cache invalidation an explicit release-checklist item.

config:cache and extension configs interact badly. If extension config files are read at cache-build time, config:cache freezes whatever existed then. Merge configs inside the extension provider (mergeConfigFrom) so they resolve at boot, and test a cold config:cache in staging before trusting the pipeline.

Silent partial activation. An extension can activate, register its provider, then fail to run migrations or publish assets — surfacing days later as a runtime error far from the cause. Add explicit post-activation assertions (a migration row exists, a published asset is present) and fail loudly in the deploy script.

Dependency ordering failures show up as a provider booting before the extension it depends on. Fix by declaring the dependency in orchestra.json's require block — not by reordering activation commands.

This is a management layer, not a security boundary. No sandboxing, no versioned host→extension API contract, no signed registry. If any roadmap item assumes third-party extension safety, scope that as separate work; do not let the word "extension" imply containment.

Don't put single-deployment code in an extension. If a feature ships to every customer and never toggles, a plain Composer package with a service provider is cheaper in every dimension — no manifest, no activation state, no migration orchestration, no drift. Reserve extensions for code that genuinely needs runtime lifecycle control.

Assume you own the upgrade path. Minimal stars, zero listed dependents, a single-maintainer profile, and a Laravel 8-era ceiling mean the honest posture is fork-and-own rather than adopt-and-follow. Budget ongoing headcount, vendor the package if you commit, and keep activation state versioned and shipped through deploys, never hand-toggled in production.

Escape hatch. If the spike fails on framework version alone, the replacement is small: native package discovery plus a modules / enabled_extensions table you control, with per-package migrate --force in the deploy script. That recovers roughly 80% of the value with a clear ownership story — write it down in the decision record before the question gets re-litigated next quarter.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings