edalzell/statamic-features
A lightweight Statamic addon for defining and managing feature flags. Toggle features via configuration, check availability in your templates, and roll out functionality safely across environments. Ideal for gradual releases, beta access, and environment-specific behavior.
edalzell/laravel-features and statamic/cms, then read the feature base class / trait that wires a laravel-feature into Statamic — that class is the map for everything else in v1.0.0. The statamic.* config options it exposes (asset handles, Vite settings) are the second place to look.addon.php config + ServiceProvider used to declare — tags, fieldtypes, modifiers, scopes, actions, widgets, dictionaries, subscribers, update scripts, blueprint/fieldset namespaces — can live inside a feature folder instead.Statamic::tag() calls in AppServiceProvider) and move it into a feature. Create the feature, list its tags/modifiers in the feature's registration arrays, delete the old manual registrations, and confirm the site still renders.class EventsFeature extends Feature
{
public function tags(): array { return [Tags\UpcomingEvents::class]; }
public function modifiers(): array { return [Modifiers\GroupByDay::class]; }
public function fieldtypes(): array { return [Fieldtypes\VenuePicker::class]; }
}
routes/web.php and resources/cp to the same feature to see the routes/assets story in one place.One feature = one domain. Group by what the editor sees ("Events", "Directory", "Donations"), not by class type. Tags, fieldtype, modifier, widget and the CP JS for that widget usually ship together and change together.
Registration lists are the primary API. Every component type in the release (tags, fieldtypes, modifiers, scopes, actions, widgets, dictionaries, subscribers, update scripts) is a declarative list on the feature. This replaces the scattered Statamic::tag() / Modifier::register() calls and the config/statamic/ overrides you'd otherwise maintain.
Namespaces replace file-path juggling. Blueprint and fieldset namespaces let a feature own resources/blueprints and resources/fieldsets and be referenced as namespace::handle from any other feature. This is the cleanest way to let site-specific features extend an addon's blueprints without copying YAML.
Routes split by intent — use them deliberately:
| File | What it's for |
|---|---|
routes/web.php |
Front-end routes on the site's domain (feeds, detail pages, webhooks) |
routes/cp.php |
Control panel routes, loaded through Statamic so they get the CP prefix + middleware — no manual auth wiring |
routes/actions.php |
Action endpoints called from the CP (modals, bulk actions) where you want Statamic's action/CSRF handling |
Keep closures out of all three if you plan to route:cache.
Assets: resources/cp by default, statamic.* when you need control. Drop resources/cp/js/cp.js and resources/cp/css/cp.css and they get registered with the CP. Reach for the config when you need a custom handle, extra entry files, or a Vite build instead of plain files. For Vite, point the config at your build directory and make sure npm run build runs in deploy — not just locally.
Migration workflow that keeps the site up: create the feature → move one component type → delete the old registration → deploy → move the next. Don't move registrations and route files in the same step; a broken route file gives a 500 that looks like a broken fieldtype.
Testing: CP routes are ordinary Laravel routes, so actingAs() a super user and hitting /cp/... works. Test the feature boundary (does the tag resolve? does the blueprint namespace resolve?), not Statamic internals.
register()-time code, not deep in boot(), or you'll get "blueprint not found" only on cold requests — and never in tests that booted something else first.config/statamic/* (or an old ServiceProvider), one silently wins. Grep for the handle before you assume a registration is broken.php please stache:clear (or php artisan statamic:stache:clear). "Fieldtype not found" after a correct registration is almost always stale stache.public/ on the server. Keep the publish step in your deploy script alongside migrate and stache:clear.route:cache after enabling/disabling a feature; a cached route table can keep serving routes for a feature you've since turned off (or miss new ones).resources/cp) — a widget with a broken JS component looks like an empty card, not an error.php please stache:doctor/the CP? → is the class autoloadable? → is the feature enabled? → is stache cleared? Almost every "the feature isn't working" report resolves at step 3 or 4.How can I help you explore Laravel packages today?