From statamic-dev
This skill should be used when the user asks to "create a custom tag", "create a modifier", "create a fieldtype", "scaffold addon", "make:tag", "make:modifier", "make:fieldtype", "make:action", "make:scope", or wants to create any Statamic extension point (tag, modifier, fieldtype, action, filter, scope, widget, dictionary).
How this skill is triggered — by the user, by Claude, or both
Slash command
/statamic-dev:create-addonThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scaffold Statamic extension points — custom tags, modifiers, fieldtypes, actions, scopes, and more — with correct boilerplate and registration.
Scaffold Statamic extension points — custom tags, modifiers, fieldtypes, actions, scopes, and more — with correct boilerplate and registration.
Identify what the user wants to create:
| Type | Directory | Base Class | Template Tag |
|---|---|---|---|
| Tag | app/Tags/ | Statamic\Tags\Tags | {{ my_tag }} |
| Modifier | app/Modifiers/ | Statamic\Modifiers\Modifier | {{ value | my_mod }} |
| Fieldtype | app/Fieldtypes/ | Statamic\Fields\Fieldtype | CP field |
| Action | app/Actions/ | Statamic\Actions\Action | CP bulk action |
| Scope | app/Scopes/ | Statamic\Query\Scopes\Scope | query_scope="name" |
| Filter | app/Scopes/Filters/ | Statamic\Query\Scopes\Filter | CP filter |
| Widget | app/Widgets/ | Statamic\Widgets\Widget | CP dashboard |
| Dictionary | app/Dictionaries/ | Statamic\Dictionaries\BasicDictionary | Dictionary fieldtype |
All directories under app/ are auto-registered by Statamic — no manual service provider registration needed for project-level extensions.
<?php
namespace App\Tags;
use Statamic\Tags\Tags;
class {ClassName} extends Tags
{
/**
* {{ {tag_name} }}
*/
public function index()
{
// Return a string for single values
// Return an array for tag pairs (loops)
return [];
}
/**
* {{ {tag_name}:{method} }}
*/
public function {methodName}()
{
return [];
}
/**
* {{ {tag_name}:* }}
*/
public function wildcard($method)
{
return "Called: {$method}";
}
}
Key tag patterns:
$this->params->get('key', 'default') — access tag parameters$this->context->get('key') — access template context/cascade$this->isPair — check if used as tag pair$this->content — get content between tag pair<?php
namespace App\Modifiers;
use Statamic\Modifiers\Modifier;
class {ClassName} extends Modifier
{
/**
* {{ value | {modifier_name}:param1:param2 }}
*/
public function index($value, $params, $context)
{
return $value;
}
}
Parameters:
$value — the value being modified$params — array of colon-separated parameters$context — full template context array<?php
namespace App\Fieldtypes;
use Statamic\Fields\Fieldtype;
class {ClassName} extends Fieldtype
{
protected $icon = '{icon}';
public $categories = ['{category}'];
protected function configFieldItems(): array
{
return [
'option_name' => [
'display' => 'Option Label',
'instructions' => 'Help text for this option.',
'type' => 'select',
'options' => [
'value1' => 'Label 1',
'value2' => 'Label 2',
],
'default' => 'value1',
],
];
}
public function preProcess($value)
{
// Transform value before sending to Vue component
return $value;
}
public function process($value)
{
// Transform value before saving to file
return $value;
}
public function augment($value)
{
// Transform value for template rendering
return $value;
}
}
<?php
namespace App\Actions;
use Statamic\Actions\Action;
class {ClassName} extends Action
{
public static function title()
{
return '{Title}';
}
public function visibleTo($item)
{
return $item instanceof \Statamic\Entries\Entry;
}
public function authorize($user, $item)
{
return $user->can('edit', $item);
}
public function run($items, $values)
{
foreach ($items as $item) {
// Perform action on each item
}
return trans_choice('{count} item processed|{count} items processed', $items->count());
}
}
<?php
namespace App\Scopes;
use Statamic\Query\Scopes\Scope;
class {ClassName} extends Scope
{
public function apply($query, $values)
{
$query->where('{field}', '{value}');
}
}
Usage in templates: {{ collection:blog query_scope="{scope_name}" }}
<?php
namespace App\Widgets;
use Statamic\Widgets\Widget;
class {ClassName} extends Widget
{
public function html()
{
return view('{view_name}', [
'data' => $this->config('data_source', 'default'),
]);
}
}
EventList, ReadTime){{ event_list }}){{ value | read_time }})toggle_password)query_scope="featured")Write the PHP class to the correct auto-registered directory. Verify the directory exists first:
mkdir -p app/{Type}s
For custom fieldtypes, create a Vue 3 component using Composition API:
<script setup>
import { Fieldtype } from '@statamic/cms';
const emit = defineEmits(Fieldtype.emits);
const props = defineProps(Fieldtype.props);
const { expose, update, meta } = Fieldtype.use(emit, props);
defineExpose(expose);
</script>
<template>
<div>
<input :value="props.value" @input="update($event.target.value)" />
</div>
</template>
Register in addon JS: Statamic.$components.register('{handle}-fieldtype', Component);
After creating the extension, display:
app/ directory extensions are auto-registeredphp please make:addon for full scaffoldFor a distributable addon package (not just a project-level extension), use:
php please make:addon vendor/package-name
This creates a full scaffold in addons/vendor/package-name/ with:
AddonServiceProviderRegister extensions in the service provider's protected arrays: $tags, $modifiers, $fieldtypes, $widgets, $commands, $routes, $vite.
npx claudepluginhub dontfreakout/claude-statamic-pluginGuides Craft CMS 5 backend extension: plugins, modules, elements, services, controllers, migrations, fields, events, queue jobs, console commands, permissions, and project config.
Scaffolds ACF Composer blocks with custom element architecture, scoped CSS, theme variations, and JS lifecycle. Use when creating new Gutenberg blocks in Sage-based WordPress themes.
Builds and reviews Wix CLI app extensions for dashboard pages, modals, widgets, backend APIs, events, and App Market readiness.