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 blueprint | Site blueprint | |
|---|---|---|
| Defines | One page's starting sections | A whole starter site: theme, pages, regions |
| Extends | AbstractBlueprint | SiteBlueprint |
| Typical use | Auto-seeding pages into sites | Provisioning a complete live site per tenant |
| Entry point | Registered on the plugin | YourStarter::provision($tenant) |
Page blueprints
A page blueprint describes one template: its name, a stable slug, and the sections it starts with. Scaffold one:
php artisan make:filamentcraft-blueprint EventHomenamespace 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 toTemplateType::Page.
Registering & auto-seeding
Register page blueprints on the plugin:
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:
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 # previewSite 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.
php artisan make:filamentcraft-blueprint EventStarter --sitenamespace 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;nullpicks the default theme.locales()— defaults to['en']; the first entry becomes the site's default locale.publish()— defaults totrue: pages are seeded published and the site goes live immediately. Returnfalseto land everything as drafts for review.siteSettings()— initial global site settings (sites.settings_json).
Provisioning
One line creates the whole thing:
$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:
EventStarterSiteBlueprint::provision($event, name: 'My Conference', slug: 'my-conf');You can also resolve the service yourself — useful when picking a preset dynamically:
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:
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
- Tenancy — how sites attach to your tenant model in the first place.
- Regions: Header & Footer — the site-wide section groups blueprints can pre-fill.
- Custom Sections — build the section classes your blueprints reference.
