---
url: /internals/package-and-boot.md
---
# 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:

```mermaid
flowchart TD
    A["Package discovery<br/>extra.laravel.providers lists 14 providers"] --> B["EvolveServiceProvider::register()"]
    B --> B1["merge the package configs, recursively"]
    B1 --> B2["resolve the module switches<br/>one per module: env, config or default"]
    B2 --> B3["bind singletons, register SettingsOverlay,<br/>define the disks"]
    B3 --> C["ModuleServiceProvider::register()<br/>once per module"]
    C -->|switch off or dependency missing| C0["returns early:<br/>nothing of the module exists at runtime"]
    C -->|on| C1["merge config/module.php"]
    C1 --> D["booting callback<br/>SettingsOverlay lays stored values over the config"]
    D --> E["EvolveServiceProvider::boot()<br/>routes, views, migrations, Livewire, Blade components,<br/>Cmd-K commands, health checks, schedule, Gate::before"]
    E --> F["ModuleServiceProvider::boot()<br/>lang, views, migrations, routes, Livewire, publish tags"]
    F --> G["bootModule() calls Evolve::registerModule()<br/>feeds sidebar, Cmd-K, Settings, search and MCP list-modules"]
```

## Core provider: register

`EvolveServiceProvider::register()` (`core/src/EvolveServiceProvider.php`) runs before any provider boots:

1. Merges the package configs: `evolve`, `image`, `translatable`, `tracking`, `activitylog`, `llm`, `health`, and `services` **recursively** (`mergeConfigRecursivelyFrom`), so a project `config/services.php` that defines `services.google` does not wipe the package keys it leaves out (2.11). Values a project config empties anyway are reported by the shadowed-config health check.
2. Resolves the module switches (`Support\ModuleSwitches::resolve()`): every key under `evolve.modules`, every module provider registered so far and the core switch `region` get one boolean. An `EVOLVE_MODULE_<KEY>` variable wins, then the config key, then the module's `defaultEnabled()`. The source of each lands in `evolve.module-sources` (`env`, `config`, `default`). A `booting` callback resolves again once project modules registered, which register after the package. Both runs end up in a cached config.
3. Binds singletons: `Deployment\Environments`, `Tracking\TrackingManager`.
4. Registers the settings store: the `SettingsOverlay` singleton and a `booting` callback that lays stored values over the config after every provider merged and before any provider boots. See [Settings overlay](/internals/settings-overlay).
5. Defines filesystem disks the package relies on: `public` rooted at `public_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()` from `config("evolve.modules.{$moduleName}", $defaultEnabled)`; after the switches resolved, the key always exists;
* returns early from `register()` and `boot()` when the module is off or `dependenciesMet()` is false, so nothing of the module exists at runtime. A module with optional packages declares them in `requiredPackages()` as package name to a class or interface the package ships (MCP: `laravel/mcp` to `Laravel\Mcp\Server`, the chatbot: `laravel/ai` to `Laravel\Ai\Contracts\Agent`); `missingPackages()` lists the ones not installed, and `dependenciesMet()` is true when that list is empty (2.21, before each module overrode `dependenciesMet()` and logged a warning on every request);
* when on, merges `config/<module>.php`, loads `lang`, `views`, `database/migrations`, `src/Http/routes.php`, and the module's Livewire components (`Services\LivewireComponentLoader`), publishes `<module>.config` and `<module>.components`, then calls `bootModule()`.

`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.
