---
url: /develop/publishing-and-overriding.md
---

# Publishing and Overriding Package Files

Evolve ships as one Composer package, `racerfish/evolve`. A project changes how a site looks or behaves by putting its own copy of a package file where Laravel looks first: a block view in the theme, a blueprint in `app/Blueprints`, a config file in `config/`. This page explains which files can be copied, where each copy has to go, how the package finds it, and how to keep copies in line when you upgrade.

The rule behind every section: **a copy never updates itself.** From the moment a file is in your project, the package can change the original and your site keeps using the old version. Copy only what you need to change, and check your copies on every upgrade.

## Package and project

| Lives in the package (`vendor/racerfish/evolve/`) | Lives in the project |
|---|---|
| Panel views, Livewire components, controllers, models, migrations, routes, translations | `config/*.php` files the starter ships or you publish |
| The `theme::` views (`head`, `consentMode`, `consentPlatform`, `trackingConfig`, `structuredData`, ...) in `theme/views/` | The theme in `theme/` (published once with `theme:publish`, then yours) |
| Blueprint sources in `core/app-publish/Blueprints` and `<module>/app-publish/Blueprints` | Blueprint classes in `app/Blueprints` |
| Module block and frontend views in `<module>/views/` | Copies of single views under `theme/views/` |
| The default config of every module and of the core | Assets in `public/vendor/evolve` (republished by Composer on every install and update) |

A fresh project from [evolve-starter](https://github.com/racerfish/evolve-starter) already carries `config/evolve.php`, `image.php`, `fortify.php`, `translatable.php`, `llm.php`, `livewire.php` and `config/view.php`. It has no `theme/` directory and no `app/Blueprints` yet: the starter README publishes both during setup (see [Installation](/getting-started/installation)). Everything else in this page is published on demand.

## Where Laravel looks for views

Evolve registers its views under namespaces: `evolve::` for the panel (`core/views`), `theme::` for the head and consent snippets (`theme/views` in the package), and one namespace per module (`blog::`, `company::`, `faq::`, ...). For every namespace, Laravel first checks `vendor/<namespace>/` inside each directory listed in `view.paths`, then falls back to the package.

The starter's `config/view.php` lists one directory:

```php
'paths' => [
    base_path('theme/views'),
],
```

So in a project built from the starter, copies go under `theme/views/vendor/<namespace>/`:

| View | Package file | Project copy |
|---|---|---|
| `evolve::components.form.input` | `vendor/racerfish/evolve/core/views/components/form/input.blade.php` | `theme/views/vendor/evolve/components/form/input.blade.php` |
| `theme::head` | `vendor/racerfish/evolve/theme/views/head.blade.php` | `theme/views/vendor/theme/head.blade.php` |
| `blog::frontend.post` | `vendor/racerfish/evolve/blog/views/frontend/post.blade.php` | `theme/views/vendor/blog/frontend/post.blade.php` |

`resources/views/vendor/<namespace>/` works only in projects whose `config/view.php` also lists `resource_path('views')`, which is the Laravel default and still the case in some older projects. Run `php artisan config:show view` when in doubt.

::: warning
The relative path inside the namespace has to match the package file exactly. A copy with a typo in its path is ignored without an error, and the package view keeps rendering.
:::

There is no publish tag for panel views. Copy the single file you need by hand. Before you do, check whether a blueprint option, a config value, a setting or CSS in the theme can do the job: every panel view copy is an upgrade task for as long as it exists.

## Blocks: blueprint and view

A block has two halves, and they are overridden separately. The internals are in [Blueprints and blocks](/internals/blueprints-and-blocks).

### Blueprints

A blueprint is a PHP class. Evolve discovers blueprints only in the directories of `evolve.blueprints.paths`, which defaults to `App\Blueprints` in `app/Blueprints`. In a real install the package does **not** read its own `app-publish/Blueprints` directories (only the [workbench](/internals/workbench) does), so a block exists in the block picker once its blueprint class is in your project. The email module is the one exception: it adds its own path for the campaign blocks, which a project does not publish.

Publish the core blueprints and those of the modules you use:

```bash
php artisan vendor:publish --tag=evolve.blueprints
php artisan vendor:publish --tag=blog.blueprints --tag=company.blueprints --tag=event.blueprints
```

From then on the files in `app/Blueprints` are yours. To change the fields of the hero block, edit `app/Blueprints/Hero.php`; there is nothing to override. When two classes declare the same component ID (`cms.hero`), the one read last wins, and a class always wins over a legacy YAML definition in `config/components/` with the same ID. See [Blueprints](/data-modeling/blueprints) for writing your own.

### Block views

The theme renders each block through `theme/views/components/common/blocks.blade.php`, which tries these in order:

1. `theme/views/components/blocks/<namespace>/<name>.blade.php` (`cms.hero` becomes `blocks/cms/hero.blade.php`)
2. a Livewire block: a view `theme/views/livewire/blocks/<namespace>/<name>.blade.php` or a Livewire component the module registered
3. the module's own view, `<module>::blocks.<name>` (since 2.6)
4. nothing: a red placeholder locally, an `<!-- evolve:missing-block -->` comment in production

The core block views come with the theme, so `theme/views/components/blocks/cms/` is already in your project after `theme:publish`. Edit them in place.

A module block renders from the package (step 3) until you put a copy at step 1. To change the company members block, copy that one view:

```bash
mkdir -p theme/views/components/blocks/company
cp vendor/racerfish/evolve/company/views/blocks/members.blade.php \
   theme/views/components/blocks/company/members.blade.php
```

`php artisan vendor:publish --tag=company.components` does the same for every company view at once, plus the YAML definitions and the `app-publish` overlay (next section). Prefer the single copy when you only need one view.

## Module publish tags

Each module offers the tags below. The `<module>.config` and `<module>.components` tags come from `ModuleServiceProvider`, the rest from the module's own provider.

| Tag | Copies |
|---|---|
| `<module>.config` | `config/<module>.php` |
| `<module>.blueprints` | the module's `app-publish/Blueprints` to `app/Blueprints` (ai-chatbot, blog, company, event, scout24) |
| `<module>.components` | the module's YAML definitions to `config/components/`, the whole `app-publish/` overlay to `app/`, and its frontend, block and Livewire views into `theme/views/` (paths per module below) |
| `ai-chatbot.views` | block view to `theme/views/components/blocks/cms` |
| `email.views` | campaign render templates to `theme/views/vendor/email/render` |
| `shopify.assets` | `public/vendor/shopify/cart-store.js` |

Where `<module>.components` puts views:

| Module | Views go to |
|---|---|
| blog | `theme/views/vendor/blog/frontend`, `theme/views/livewire/blocks/blog` |
| company | `theme/views/vendor/company/frontend`, `theme/views/components/blocks/company` |
| event | `theme/views/vendor/event/frontend`, `theme/views/components/blocks/events` |
| faq | `theme/views/vendor/faq/faq`, `theme/views/vendor/faq/frontend`, `theme/views/components/blocks/faq` |
| locator | `theme/views/vendor/locator/frontend`, `theme/views/components/blocks/locator`, `theme/views/livewire/blocks/locator` |
| project | `theme/views/vendor/project/project`, `theme/views/vendor/project/frontend` |
| scout24 | `theme/views/vendor/scout24/vehicle`, `theme/views/components/blocks/scout24` |
| shopify | `theme/views/vendor/shopify/frontend`, `theme/views/components/blocks/shopify` |

The tag list of an install is also available from `php artisan vendor:publish` without arguments, which asks which provider or tag to publish. Module tags only exist while the module is enabled.

### The `app-publish` overlays

`app-publish/` in a module mirrors the project's `app/` directory. It holds the blueprint classes and, for two modules, a Livewire block class:

| Overlay file | Published to | Effect |
|---|---|---|
| `blog/app-publish/Livewire/Blocks/Blog/Posts.php` | `app/Livewire/Blocks/Blog/Posts.php` | The blog registers `App\Livewire\Blocks\Blog\Posts` for `blocks.blog.posts` when that class exists, otherwise its own. |
| `locator/app-publish/Livewire/Blocks/Locator/Locations.php` | `app/Livewire/Blocks/Locator/Locations.php` | Same for the store finder. |

The overlay is copied when you run `<module>.blueprints` (blueprints only) or `<module>.components` (everything). It is never copied on `composer update`. Once the Livewire class is in `app/`, it replaces the package class completely, so it misses every fix the package makes to the original. Publish it only to change the component's logic, and compare it with `vendor/racerfish/evolve/<module>/app-publish/` after each upgrade.

## Core and theme publish tags

| Tag | Copies |
|---|---|
| `evolve.config` | `config/evolve.php`, `image.php`, `fortify.php`, `translatable.php` |
| `evolve.blueprints` (alias `evolve.components`) | `core/app-publish/Blueprints` to `app/Blueprints` |
| `evolve.legacy-components` | the core YAML block definitions to `config/components/` |
| `evolve.image`, `evolve.translatable`, `evolve.llm` | `config/image.php`, `config/translatable.php`, `config/llm.php` |
| `evolve.assets` | the panel build to `public/vendor/evolve` (the starter republishes it with `--force` after every `composer install` and `composer update`) |
| `evolve.deploy-exclude` | `deploy_exclude.txt` |
| `evolve.claude.skill`, `evolve.claude.starter` | Claude Code files, see [MCP Server](/modules/mcp-server) |
| `theme` | the whole starter theme to `theme/` and `tsconfig.json`; use `php artisan theme:publish` |
| `theme.config` | `config/theme.php` |
| `theme.views` | the `theme::` views to `resources/views/vendor/theme` |

`theme.views` targets `resources/views`, which a starter project does not read (see [Where Laravel looks for views](#where-laravel-looks-for-views)). In such a project, copy the single `theme::` view you need to `theme/views/vendor/theme/` instead.

`vendor:publish` skips files that already exist. Running a tag again after an upgrade therefore only adds new files, for example a blueprint a release introduced. `--force` overwrites, and with it every change you made to the copies. `php artisan theme:publish --force` does that for the whole theme.

## Config files

The package merges its own config under yours. Laravel's merge works on the **top level only**: a key you have in `config/evolve.php` replaces the package value for that key as a whole. A new top level key in a release arrives without you doing anything. A new nested key under a top level key your file already has does not, because your array replaces the package array.

`config/services.php` is the exception: Evolve merges it recursively, and the [Shadowed config](/site/health-checks#shadowed-config) health check reports package values a project file empties. Details in [Package layout and boot](/internals/package-and-boot).

Publish a config only to change a value that has no `.env` key or setting. `config/health.php` and `config/tracking.php` have no publish tag. The package merges them, and their values come from `.env` keys and, for many of them, the [Settings Store](/panel/settings-store).

## What not to copy

* **Controllers, models, Livewire panel components, services.** Copying the file does nothing, because the package loads its own classes by namespace. Extend the class and swap it in the container, see [Extending Evolve](/custom-modules/extending-evolve#extending-models-and-controllers).
* **Migrations.** The package loads them from `vendor/` and has no publish tag for them. Leave them there.
* **Translations.** Override single keys in `lang/vendor/<namespace>/` rather than copying the whole file.
* **The whole panel.** Copy single views, never a directory.

## Keeping copies in sync on upgrade

`php artisan evolve:view-overrides` lists your copies of `evolve::` and `theme::` views and the package changes each copy has to follow. It looks in every `vendor/evolve` and `vendor/theme` directory under your view paths, so it finds copies in `theme/views/vendor/` as well as in `resources/views/vendor/`. Run it with the version you upgrade **from**:

```bash
php artisan evolve:view-overrides --since=2.18.0
```

The table shows each copy with the number of changes released after that version, or `none known`. Below the table every affected view gets its changes spelled out. `--strict` exits with 1 while a copy has changes to follow, for a deploy script or CI job.

The changes come from a registry in the package, `core/src/Support/ViewOverrides.php`:

* `CHANGES`: per view, the version and what a copy has to change. Every release that changes a view a project might have copied adds an entry there.
* `REMOVED`: views the package deleted. A copy of one is reported as safe to delete.
* `PATTERNS`: markup that breaks in any panel view copy, whatever its name (for example Font Awesome tags since 2.13). Frontend, mail and `theme::` views are not checked against them.

Module views (`blog::`, `company::`, ...), block views in `theme/views/components/blocks/` and the `app-publish` overlays are **not** covered. Compare them by hand after each upgrade:

```bash
diff vendor/racerfish/evolve/company/views/blocks/members.blade.php \
     theme/views/components/blocks/company/members.blade.php
```

The upgrade guide has the full workflow and the log of every change so far in [Overridden Panel Views](/getting-started/upgrading#overridden-panel-views).
