orchestra/theme-installer
Composer installer for Orchestra Platform themes that automatically places theme packages in the correct themes directory, streamlining installation and discovery within Orchestra applications.
This is not an application-level library — you will never call a class from it in a controller or Blade view. It is a Composer plugin, so "getting started" means getting your composer.json and CI to accept it, then watching theme packages land in themes/ instead of vendor/.
Prerequisites. You are on an Orchestra Platform application (or a fork). This package has meaning only in that context; there is nothing here for vanilla Laravel. Confirm the platform major you run is the one this plugin's release line targets — the v1.1.0 notes ship no compatibility matrix.
Steps.
composer require orchestra/theme-installer
composer require acme/theme-bootstrap # any package with "type": "orchestra-theme"
composer.json:
"config": {
"allow-plugins": {
"orchestra/theme-installer": true
}
}
Do this before you run composer install inside Docker or CI, or your first pipeline run becomes your discovery mechanism.themes/<name>/, not vendor/acme/theme-bootstrap/. Run composer install -vvv if you need to see which installer claimed the package.Where to look first.
composer.json — its "type" field is the entire contract. If it isn't exactly orchestra-theme, nothing else here applies.Composer\Installer\InstallerInterface). Two methods matter: supports() (where the orchestra-theme type check lives) and getInstallPath() (where the themes/ path is derived). Everything else — install, update, uninstall — funnels through those two decisions.First use case. Swap a hand-copied theme folder for a versioned package: composer require acme/theme-bootstrap:^2.0, delete the old directory, commit the composer.lock diff. You've just made theme rollback a git revert instead of an SFTP job.
Theme-as-package distribution. Each theme ships as its own repo/package with "type": "orchestra-theme" and a semver tag. Consumers only ever see composer require acme/theme-x:^1.2. This is the only pattern where the plugin earns its keep — the theme code lives in themes/ (discoverable, greppable, editable during development) while remaining version-pinned via the lockfile.
Pin the plugin explicitly. Do not float on ^1.0 for a package with zero dependents and a stale PHP floor. Use an exact or tilde constraint (~1.1.0) and review the composer.lock diff on every bump. Treat the lockfile diff as the review artifact, not the release notes.
Multi-brand / per-tenant theming (staged). Two viable shapes:
themes/ subdirectory to load at runtime. The plugin just guarantees all of them are on disk.This plugin installs files. Theme activation, switching, and per-tenant assignment are your domain logic, not its.
CI/CD integration. The plugin must be whitelisted in the environment running composer install --no-interaction. Practical checklist:
allow-plugins committed to composer.json (not set via CLI flag per-run, which drifts).themes/ added to the deploy artifact manifest — if your build assumes "repo + vendor/ is the artifact," it isn't anymore..gitignore decision: exclude themes/ (treat as Composer-owned, like vendor/) or commit it (defeats the purpose). Pick one and document it.Fork-and-own. Realistic for a zero-dependent package: copy the installer class into app/../packages/ or an internal Composer plugin you own. It's roughly 100–150 lines — supports(), getInstallPath(), install/update/uninstall. You gain a maintainer, a PHP version you actually run, and a CI matrix. You lose nothing, because there is no upstream community to stay current with.
Verification spike before adopting. On a throwaway branch: install the plugin + one theme, run composer update, run composer remove on the theme, and change the configured themes path. Confirm where files land and, critically, what composer remove leaves behind. Do this before the plugin reaches a shared environment.
The silent misconfiguration. A theme package with "type" misspelled or absent falls through to Composer's default installer and lands in vendor/ — with no error. The symptom appears much later as "theme not found." When a theme seems missing, check its "type" field before debugging the plugin.
allow-plugins is a governance decision, not a config tweak. Composer 2.2+ blocks plugins by default because plugins execute arbitrary code at install time. Someone needs to sign off on whitelisting this. Do that in parallel with the technical spike, not after — it's the longer pole.
Custom vendor-dir breaks naive path resolution. If getInstallPath() derives the themes directory relative to the project root vs. the vendor directory, a non-default vendor-dir (common in monorepos and some Docker setups) can send themes to the wrong place. Verify with your actual vendor-dir setting, not the default.
Uninstall cleanup is the weak spot. Confirm whether composer remove acme/theme-bootstrap deletes themes/theme-bootstrap/ or leaves an orphan directory. Orphans are worse than they sound: the app may keep loading stale theme files that no longer have a lockfile entry. Check this in the spike; if it leaves debris, add a post-remove script or a CI cleanup step.
PHP 7.1 floor, unvalidated on 8.x. v1.1.0 sets a 7.1 minimum and shows no evidence of a PHP 8.1–8.3 CI matrix. The dependency-hygiene change — removing illuminate/support — is potentially breaking for anyone who relied on it transitively, and shipped as a minor bump. Before upgrading from a pre-1.1 line, confirm nothing in your code depended on that package and that the plugin source no longer touches Illuminate\ classes or Laravel global helpers. Budget ~half a day; the audit is the go/no-go gate, not the release notes.
Debugging. composer install -vvv prints the installer class that claimed each package — the fastest way to distinguish "plugin not whitelisted," "plugin didn't match the type," and "plugin matched but resolved the wrong path." Composer's install-time errors surface here, not as application stack traces, so front-line triage should escalate to whoever owns the plugin rather than debugging theme code.
Extension points are minimal by design. There is no service provider, no config file, no events. Your leverage points are: the package "type" string, the allow-plugins entry, and (if forked) the two installer methods. Don't look for a hooks API that isn't there.
Don't reach for this on vanilla Laravel. Blade namespaces, view path registration, and a Vite/asset build step already solve theme delivery. composer/installers handles generic custom install paths for a wide range of package types and is far better maintained — prefer it unless the orchestra-theme type is a hard platform requirement.
How can I help you explore Laravel packages today?