Package layout and boot
How racerfish/evolve is put together and what happens between composer install and the first request.
One package, many providers
composer.json declares the package racerfish/evolve (PHP 8.3, tested against Laravel 12 through orchestra/testbench). Its PSR-4 map points Racerfish\Evolve\ at core/src/ and one namespace per module at <module>/src/ (Racerfish\Blog\, Racerfish\Shopify\, Racerfish\Mcp\, Racerfish\AiChatbot\, Racerfish\Email\, and so on). core/global/helpers.php is autoloaded as a file and holds the global helpers such as module_index_url().
extra.laravel.providers lists fourteen providers for package discovery: the twelve module providers, Racerfish\Theme\ThemeServiceProvider and Racerfish\Evolve\EvolveServiceProvider. A host application therefore needs no provider registration of its own. Optional packages are suggested rather than required: laravel/mcp for the MCP module and laravel/ai for the chatbot (both since 2.21, in require-dev so the suite covers the modules), and sentry/sentry-laravel for error tracking.
Directory roles:
| Path | Role |
|---|---|
core/ | the CMS: src/, config/, database/, routes/, views/, lang/, assets/ (panel build), bin/ (Envoy, deploy_exclude.txt), app-publish/ (blueprints published into projects), stubs/claude/ (CLAUDE.md, skill and .mcp.json for client projects), docs/, tests/ |
<module>/ | src/, config/<module>.php, config/components/*.yaml, database/migrations, views/, lang/, optionally app-publish/ and tests/ |
theme/ | ThemeServiceProvider, the theme:: views the package renders (head partials, structured data, mail), and theme/theme/, the publishable starter theme |
workbench/ | Testbench host app for development and tests, not shipped |
.github/ | CI, Release Drafter, RC and semver-label workflows, the docs release entry script |
Boot sequence
What Laravel runs between autoload and the first request, and where Evolve hooks in:
Core provider: register
EvolveServiceProvider::register() (core/src/EvolveServiceProvider.php) runs before any provider boots:
- Merges the package configs:
evolve,image,translatable,tracking,activitylog,llm,health, andservicesrecursively (mergeConfigRecursivelyFrom), so a projectconfig/services.phpthat definesservices.googledoes not wipe the package keys it leaves out (2.11). Values a project config empties anyway are reported by the shadowed-config health check. - Resolves the module switches (
Support\ModuleSwitches::resolve()): every key underevolve.modules, every module provider registered so far and the core switchregionget one boolean. AnEVOLVE_MODULE_<KEY>variable wins, then the config key, then the module'sdefaultEnabled(). The source of each lands inevolve.module-sources(env,config,default). Abootingcallback resolves again once project modules registered, which register after the package. Both runs end up in a cached config. - Binds singletons:
Deployment\Environments,Tracking\TrackingManager. - Registers the settings store: the
SettingsOverlaysingleton and abootingcallback that lays stored values over the config after every provider merged and before any provider boots. See Settings overlay. - Defines filesystem disks the package relies on:
publicrooted atpublic_path(),uploads,logs,evolveRoot,evolveModules,theme.
Core provider: boot
boot() calls bootEvolve(), which loads routes (core/routes/routes.php), views (evolve::), translations, migrations and Livewire components, registers Blade components and the Route::evolveModule() macro, the panel commands for Cmd-K, the health checks and the schedule (health checks and heartbeats every minute, history pruning daily). Then it registers Gate::before with Support\Permissions::before, excludes the consent and tracking cookies from cookie encryption (frontend JavaScript writes evolve_consent, PHP reads it back, and _ga, _fbp, _fbc are forwarded to the conversion APIs), and wires Fortify's views and actions to Evolve's auth screens.
Route::evolveModule($name, $group) wraps a module's panel routes in web, auth and backend.locale middleware, the prefix evolve/<name>, the name prefix <name>., and the role middleware resolved from evolve.roles.modules.
Module providers
Every module extends core/src/ModuleServiceProvider.php and sets $moduleName and $modulePath. The base class:
- answers
isEnabled()fromconfig("evolve.modules.{$moduleName}", $defaultEnabled); after the switches resolved, the key always exists; - returns early from
register()andboot()when the module is off ordependenciesMet()is false, so nothing of the module exists at runtime. A module with optional packages declares them inrequiredPackages()as package name to a class or interface the package ships (MCP:laravel/mcptoLaravel\Mcp\Server, the chatbot:laravel/aitoLaravel\Ai\Contracts\Agent);missingPackages()lists the ones not installed, anddependenciesMet()is true when that list is empty (2.21, before each module overrodedependenciesMet()and logged a warning on every request); - when on, merges
config/<module>.php, loadslang,views,database/migrations,src/Http/routes.php, and the module's Livewire components (Services\LivewireComponentLoader), publishes<module>.configand<module>.components, then callsbootModule().
bootModule() is where a module calls Evolve::registerModule($name, [...]) with name, path, optional label, commands (Cmd-K entries built with Support\PanelCommand), settings (keys, booleans, secrets the Settings screen may write; config maps each key to the config path the screen renders it from so an untouched default is not written to the .env; required maps the env keys the module needs to their config path for the API keys health check), searchable models and frontend.translations. Evolve::registerModule() ignores a module whose switch is off. The registry feeds the sidebar, the command palette, the Settings "Packages" tab, global search and the MCP list-modules tool.
The Evolve singleton
core/src/Evolve.php (facade Evolve) is the registry: modules, block composers (registerBlockComposer), discovered blueprints (evolve.blueprints.paths plus legacy YAML), roles per area (rolesFor('media'), rolesForModule('blog'), roleMiddleware()), the environment type (environmentType()) and emitsTracking().
Publishing
Core tags: evolve.config (evolve, image, fortify, translatable), evolve.blueprints and evolve.components, evolve.assets (the built panel to public/vendor/evolve), evolve.mail-views (the mail document layout and block views to theme/views/vendor/evolve/email, 2.22), evolve.llm, evolve.deploy-exclude, evolve.claude.starter and evolve.claude.skill. Modules add <module>.config, <module>.components and, where they ship them, <module>.blueprints, <module>.views or <module>.assets. The health config is deliberately not publishable.
Reading order
For a change in the core, start at the provider method that wires the area, then the service under core/src/Services, Support, Tracking, Settings, Health or Deployment, then the Livewire component or controller under core/src/Http, then the view under core/views. The other pages of this track follow that path per system.