Skip to content

Seeding & Blueprints

When a tenant signs up — an event, a place, a store — their site shouldn't start as an empty editor. Blueprints let you define starter content in code and seed it automatically, so every new tenant lands on a real, working site.

There are two layers, and they compose:

Page blueprintSite blueprint
DefinesOne page's starting sectionsA whole starter site: theme, pages, regions
ExtendsAbstractBlueprintSiteBlueprint
Typical useAuto-seeding pages into sitesProvisioning a complete live site per tenant
Entry pointRegistered on the pluginYourStarter::provision($tenant)

Page blueprints

A page blueprint describes one template: its name, a stable slug, and the sections it starts with. Scaffold one:

bash
php artisan make:filamentcraft-blueprint EventHome
php
namespace App\Blueprints;

use FilamentCraft\Blueprints\AbstractBlueprint;
use FilamentCraft\Blueprints\BlueprintSection;
use FilamentCraft\Sections\Builtin\FaqSection;
use FilamentCraft\Sections\Builtin\HeroSection;

final class EventHomeBlueprint extends AbstractBlueprint
{
    public function name(): string
    {
        return 'Home';
    }

    public function slug(): string
    {
        return 'event-home';
    }

    public function sections(): array
    {
        return [
            BlueprintSection::make(HeroSection::class)
                ->settings(['heading' => 'Welcome to the event']),

            BlueprintSection::make(FaqSection::class),
        ];
    }

    public function isHomepage(): bool
    {
        return true;
    }
}

BlueprintSection::make() accepts any section class — built-in or your own. Settings you pass are merged over the section's defaults, so you only specify what differs. Two more knobs:

  • ->lock() — the section can't be removed or reordered in the editor.
  • ->hide() — seeded hidden; the user can reveal it when ready.

And on the blueprint class itself:

  • isHomepage() — the seeded template claims the site's homepage slot (first one wins).
  • sealed() — hides the "Add section" button for pages seeded from this blueprint, giving tenants a fill-in-the-blanks page instead of a free-form one.
  • type() — defaults to TemplateType::Page.

Registering & auto-seeding

Register page blueprints on the plugin:

php
FilamentCraftPlugin::make()
    ->registerBlueprint(EventHomeBlueprint::class)
    // or discover a whole directory:
    ->discoverBlueprintsIn(app_path('Blueprints'))

By default (filamentcraft.blueprints.auto_seed), every registered blueprint is seeded into each newly created Site as a draft template — the SiteCreated event listener handles it. Each template remembers its blueprint_slug, so re-running never duplicates.

For existing sites, seed from the console:

bash
php artisan filamentcraft:seed-blueprints            # every site
php artisan filamentcraft:seed-blueprints --site=3   # one site
php artisan filamentcraft:seed-blueprints --owner=7  # all sites of an owner
php artisan filamentcraft:seed-blueprints --dry-run  # preview

Site blueprints — starter presets per tenant

A site blueprint is the full preset: which pages, which theme, what header and footer content, and whether the site goes live immediately. Define one per tenant kind — an event starter, a place starter — and provision it when the tenant is created.

bash
php artisan make:filamentcraft-blueprint EventStarter --site
php
namespace App\Blueprints;

use FilamentCraft\Blueprints\BlueprintRegion;
use FilamentCraft\Blueprints\BlueprintSection;
use FilamentCraft\Blueprints\SiteBlueprint;
use FilamentCraft\Enums\RegionName;
use FilamentCraft\Sections\Builtin\FooterSection;
use FilamentCraft\Sections\Builtin\HeaderSection;

final class EventStarterSiteBlueprint extends SiteBlueprint
{
    public function name(): string
    {
        return 'Event starter';
    }

    public function pages(): array
    {
        return [
            EventHomeBlueprint::class,
            EventScheduleBlueprint::class,
        ];
    }

    public function regions(): array
    {
        return [
            BlueprintRegion::make(RegionName::Header)->sections([
                BlueprintSection::make(HeaderSection::class),
            ]),
            BlueprintRegion::make(RegionName::Footer)->sections([
                BlueprintSection::make(FooterSection::class),
            ]),
        ];
    }
}

Everything else has sensible defaults you can override:

  • themeSlug() — theme for the new site; null picks the default theme.
  • locales() — defaults to ['en']; the first entry becomes the site's default locale.
  • publish() — defaults to true: pages are seeded published and the site goes live immediately. Return false to land everything as drafts for review.
  • siteSettings() — initial global site settings (sites.settings_json).

Provisioning

One line creates the whole thing:

php
$site = EventStarterSiteBlueprint::provision($event);

In a single database transaction this creates the Site (morph-attached to the owner you pass), resolves the theme, seeds every page with a first revision, marks the homepage (isHomepage() page, or the first page as a fallback), writes the region content, and — when publish() is true — publishes every page and flips the site live. The tenant can open the editor or their public URL immediately.

The site name defaults to the owner's name attribute (falling back to the blueprint name), and the slug is derived and de-duplicated automatically. Override either:

php
EventStarterSiteBlueprint::provision($event, name: 'My Conference', slug: 'my-conf');

You can also resolve the service yourself — useful when picking a preset dynamically:

php
use FilamentCraft\Blueprints\SiteProvisioner;

$blueprint = $tenant->kind === 'place'
    ? PlaceStarterSiteBlueprint::class
    : EventStarterSiteBlueprint::class;

app(SiteProvisioner::class)->provision($blueprint, $tenant);

The tenant-onboarding recipe

Hook provisioning into your tenant lifecycle with a model observer:

php
namespace App\Observers;

use App\Blueprints\EventStarterSiteBlueprint;
use App\Models\Event;

final class EventObserver
{
    public function created(Event $event): void
    {
        EventStarterSiteBlueprint::provision($event);
    }
}

That's the entire integration. No Laravel seeders, no manual model wiring — every new tenant gets a complete, live site the moment their row exists.

Provisioning suspends auto-seeding

While provision() runs, the global auto-seed listener is suspended — the new site gets exactly the pages the site blueprint lists, not every registered blueprint. Pages listed in a site blueprint don't need to be registered on the plugin. The exception is sealed() pages: the editor looks blueprints up by slug to honour sealed(), so register those with registerBlueprint() too (and set filamentcraft.blueprints.auto_seed to false if you only ever provision through site blueprints).

Why class-strings instead of enums?

Sections are referenced by class (HeroSection::class), not by an enum case. That's deliberate: the section catalog is an open set — your app (and your customers' apps) add new section classes the package can't know about, so no enum could enumerate them. A class-string gives you the same safety an enum would: IDE autocompletion, refactor-safe renames, and "does this exist" validation when the blueprint is built.

Where a set is closed, the API does use real enums: regions are keyed by RegionName::Header / RegionName::Footer / RegionName::Announcement, template types by TemplateType, and site status by SiteStatus.

Next steps

Proprietary — distributed via Anystack.