---
url: /internals/blueprints-and-blocks.md
---
# Blueprints and blocks

Content in Evolve is a tree of blocks on a page (or on any "blockable" record). A blueprint describes a block type: its fields, label, group and icon. A block is one instance with its field values per locale.

## Blueprint definitions

A blueprint is a PHP class implementing `Racerfish\Evolve\Contracts\BlueprintConfig` with one static method, `config(): Blueprint`:

```php
class Hero implements BlueprintConfig
{
    public static function config(): Blueprint
    {
        return Blueprint::make('cms.hero')
            ->label('Hero')
            ->icon('heading')
            ->allowMedia(accept: ['image', 'video'])
            ->fields([
                Field::string('title', 'Titel'),
                Field::richtext('content', 'Inhalt'),
                Field::fieldset('buttons', 'Buttons', Button::class)->repeatable(),
            ]);
    }
}
```

`Blueprint::make()` (`core/src/DTO/Blueprint.php`) takes the component ID (`namespace.name`) and accepts label, group, fields, `selectable` (whether the block picker offers it; the email blocks pass `false` so they only exist inside campaigns), icon and a media setting either as named arguments or through the fluent setters (`->label()`, `->group()`, `->icon()`, `->allowMedia()`, `->fields()`, `->hidden()`). `Field` (`core/src/DTO/Field.php`) builds fields through static constructors named after the type (`string`, `text`, `textarea`, `richtext`, `html`, `number`, `url`, `address`, `select`, `radioGroup`, `resource` with a model, repeatables and more) with label, default, width and visibility options.

**Discovery.** `Evolve::getBlueprints()` scans `evolve.blueprints.paths`, a map from namespace to one or more directories (several paths per namespace since 2.5). The default is `App\Blueprints` → `app/Blueprints`; modules extend it (the email module adds `Racerfish\Email\Blueprints`) and the workbench points it at every module's `app-publish/Blueprints`. Projects publish module blueprints into `app/Blueprints` with the `<module>.blueprints` tags, which is why a published blueprint can be edited per project.

**Legacy YAML.** With `evolve.blueprints.load-legacy-defintions` on (default), `config/components/*.yaml` files are read too (`name`, `component`, `icon`, `block`, `fields`, `titlePreview`, `allowNestedBlocks`). The module `config/components` directories still ship YAML for their blocks, published with `<module>.components`.

`Blueprint::all()`, `allGrouped()` and `for('cms.cards')` are the lookups the block editor and the MCP `available-block-types-resource` use. `evolve:blueprint` scaffolds a class and tells you where the view goes; `evolve:component` scaffolds a component with fields; `evolve:typings` emits TypeScript typings.

## The Block model

`core/src/Models/Block.php`:

* `props` holds the field values as JSON keyed by locale; `getFieldsAttribute()` hydrates them through the blueprint, resolving `resource` references to models (a dangling reference becomes `null` since 2.4.1) and media IDs to `Media`.
* `blockable` is a morph to the owner (page, post, campaign); children are blocks whose blockable is a block, so containers (`cms.cards`, `cms.tiles`, `cms.logoCloud`) nest.
* Editor link: `getSearchableAdminUrl()` asks the blockable for its editor URL through `Contracts\BlockContainer::blockEditUrl()`. `Block` implements it by asking its own blockable, so a nested block resolves to the root carrier. Page, Section and every module model with a block editor implement the contract; the core imports no module model for this since 2.21. Before that a `switch` over six module classes decided, and it missed Shopify collections. Without the contract the link falls back to `evolve.page.show`.
* Publishing: `is_published`, `publish_at`, `unpublish_at`; `scopePublished()` and `is_currently_published` apply the schedule. The MCP read tools and the frontend use the published scope; the panel preview may include unpublished blocks.
* Clones: a block can reference a global section's block (`is_clone`), which is how [global sections](/panel/) reuse content; saving a section forgets the cached rendering of every clone.
* Regions: `getContentRegion()` resolves the region whose overrides apply, see [Regions](/modules/regions).
* `anchor` gives every block an `id` on the site; `exportContentSnippet()` and `importContentSnippet()` carry a locale's texts out and back in for translation.

## Rendering

The theme renders a page's blocks through the block view for the type: `theme/views/components/blocks/<namespace>/<name>.blade.php` (`cms.cards` → `blocks/cms/cards.blade.php`). Module blocks render from the module's own `views/blocks` when the project has no copy (since 2.6); a block with no view at all leaves an `<!-- evolve:missing-block -->` comment in production.

Views receive the block with its hydrated fields. When a block needs data beyond its fields, a **block composer** supplies it: `Evolve::registerBlockComposer('blog.posts', BlogPostsComposer::class)` maps a component to a class that prepares the view data (the posts for the list, with pagination). Core ships `BlockComposers\GlobalsComposer`; blog, company and others register theirs in `bootModule()`.

Livewire blocks (the posts list, the store finder, the chatbot) are Livewire components the module registers; the theme mounts them when `Evolve::hasLivewireBlock($name)` finds the class.

## The block editor

The panel's block editor is a set of Livewire components under `core/src/Http/Livewire` (`BlockEditor`, the field components, `BlockContentGenerator` for the AI page generator) with views under `core/views/block`. Field types map to field views under `core/views/components/form`. The editor keys its field container by locale so values never bleed across languages (2.4.2), and takes placeholders and the "Default" option from the page region's fallback locale (2.12).

## Where to look

| Question | Path |
|---|---|
| What fields does a core block have | `core/app-publish/Blueprints/*.php` |
| How a field type renders in the panel | `core/views/components/form/*.blade.php` |
| How props become fields | `Block::getFieldsAttribute()` and `hydrateProps()` |
| Which blueprints an install knows | Devtools > Block Blueprints, or `Blueprint::all()` |
| How a block appears on the site | `theme/theme/views/components/blocks/` in the starter theme |
