tdwesten/statamic-builder
A Statamic addon by tdwesten that streamlines building and scaffolding custom Statamic addons, giving developers a practical starting point for creating, organizing, and managing addon code more efficiently in Laravel and Statamic projects.
Install and publish config
composer require tdwesten/statamic-builder
php artisan vendor:publish --tag="statamic-builder-config"
The package is a thin code-generation layer: you write PHP classes, it emits the YAML that Statamic actually reads. Nothing changes at runtime unless you run the build step.
Where to look first
vendor/tdwesten/statamic-builder/src is the real documentation).resources/blueprints, content/*/*.yaml, and resources/fieldsets — those are what you'll be replacing.First use case: port one collection, not all of them
Pick a small, stable collection (e.g. blog). Generate the builder class with the package's make:* artisan command (e.g. php artisan make:collection Blog, php artisan make:blueprint, make:fieldset, make:navigation, make:taxonomy), fill in the fields, run the package's build/sync command, then diff the generated YAML against what was there before. If the diff is empty or trivial, you're ready to scale.
// app/Statamic/Collections/Blog.php
class Blog extends Collection
{
public function handle(): string { return 'blog'; }
public function title(): string { return 'Blog'; }
public function routes(): array
{
return ['/blog/{slug}'];
}
public function fields(): array
{
return [
Field::make('title')->type('text')->required(),
Field::make('slug')->type('slug'),
Field::make('content')->type('markdown'),
];
}
}
One class per artifact, grouped by type. Keep app/Statamic/Collections, Blueprints, Fieldsets, Navigations, Taxonomies. Handles and titles are returned from methods; everything else is fluent config. This keeps the "what" (structure) separate from the "where" (registration).
Compose with fieldsets, not copy-paste. Any field group used by more than one blueprint (SEO, hero, CTA) becomes a Fieldset class and is referenced from blueprints. This is the single biggest maintenance win over YAML — in YAML you duplicate, here you use another class.
class Hero extends Fieldset
{
public function handle(): string { return 'hero'; }
public function fields(): array
{
return [
Field::make('heading')->type('text'),
Field::make('image')->type('assets')->container('assets'),
];
}
}
// inside a Blueprint's fields():
Field::make('hero')->type('group')->fields(Hero::make()->fields()),
Treat PHP as source of truth, YAML as build output. The workflow that scales is:
edit PHP → run build command → commit generated YAML → deploy
Add the build step to your local setup (a composer script or a make build-content target) and to CI as a drift check: run the build and fail if git diff is non-empty. That catches "someone edited YAML in prod" immediately.
Reuse beyond fields. Collection routes(), taxonomies(), dated(), orderable() etc. are just methods — so you can build a small abstract BaseCollection with common behavior and subclass it per collection. Custom field types are the same: wrap repetitive config in a factory method (Field::make('price')->type('money')->config([...])) and call it everywhere.
Environment split. Keep builder classes in app/ (autoloaded in every env), but only run generation in local/CI. Production should ship the committed YAML. If you rely on the package's auto-discovery at boot, verify it doesn't regenerate or rewrite files on deploy.
Testing. Because the definitions are plain PHP objects, you can instantiate a collection in a test and assert on fields(), handles, and routes without touching Statamic's Stache. That's a real advantage for large multi-site projects.
php artisan statamic:stache:clear (or cache:clear in dev) after generating, or you'll debug phantom "field doesn't exist" errors for an hour.->type('markdownn') won't error at build time — it produces YAML Statamic can't resolve, and the field just vanishes in the CP. Double-check handles/types against php artisan statamic:...-listed addons or the fieldtype source.visibility, validate, width, conditional logic, replicator sets with nested configs), expect to fall back to a ->config([...]) / raw array escape hatch. Grep the vendor source for the method before assuming it exists.fields() returning a stable array order; associative arrays or loops that depend on iteration order will produce noisy YAML diffs on every rebuild.composer.lock and read the changelog before upgrading. Check the composer constraint against your Statamic version first.resources/blueprints. Best mitigation: only regenerate when definitions actually change, and review the diff as part of the PR rather than committing build noise.dd() the class's fields() return value, (2) inspect the emitted YAML file directly, (3) php artisan statamic:stache:clear, (4) re-check the CP. If the YAML is right but the CP is wrong, it's a caching problem, not a builder problem.How can I help you explore Laravel packages today?