Skip to content

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 reuse content; saving a section forgets the cached rendering of every clone.
  • Regions: getContentRegion() resolves the region whose overrides apply, see 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 ​

QuestionPath
What fields does a core block havecore/app-publish/Blueprints/*.php
How a field type renders in the panelcore/views/components/form/*.blade.php
How props become fieldsBlock::getFieldsAttribute() and hydrateProps()
Which blueprints an install knowsDevtools > Block Blueprints, or Blueprint::all()
How a block appears on the sitetheme/theme/views/components/blocks/ in the starter theme