Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Creating Statamic Addons

This guide walks you through creating a Statamic addon from scratch. We'll build a real addon that adds functionality to your Statamic installation.

What You're Building

By the end of this guide, you'll have:

  • A working Statamic addon
  • A custom fieldtype
  • Control Panel routes and pages
  • Assets compiled with Vite
  • A solid foundation to build upon

Step 1: Generate the Addon

Use Statamic's built-in command to scaffold your addon:

php please make:addon vendor/my-addon

This creates the basic structure in your addons directory:

addons/
    vendor/
        my-addon/
            src/
                ServiceProvider.php
            resources/
            tests/
            composer.json
            README.md

Step 2: Understanding composer.json

Your addon's composer.json is crucial. Here's what Statamic needs:

{
    "name": "vendor/my-addon",
    "description": "My amazing Statamic addon",
    "type": "statamic-addon",
    "autoload": {
        "psr-4": {
            "Vendor\\MyAddon\\": "src"
        }
    },
    "authors": [
        {
            "name": "Your Name",
            "email": "you@example.com"
        }
    ],
    "extra": {
        "statamic": {
            "name": "My Addon",
            "description": "My amazing Statamic addon"
        },
        "laravel": {
            "providers": [
                "Vendor\\MyAddon\\ServiceProvider"
            ]
        }
    },
    "require": {
        "statamic/cms": "^5.0"
    }
}

The extra.statamic section tells Statamic this is an addon. The extra.laravel.providers section registers your service provider with Laravel.

Step 3: The Service Provider

Your service provider is the heart of your addon. It must extend Statamic's AddonServiceProvider:

<?php

namespace Vendor\MyAddon;

use Statamic\Providers\AddonServiceProvider;

class ServiceProvider extends AddonServiceProvider
{
    protected $vite = [
        'input' => [
            'resources/js/cp.js',
            'resources/css/cp.css'
        ],
        'publicDirectory' => 'resources/dist',
    ];

    protected $routes = [
        'cp' => __DIR__.'/../routes/cp.php',
        'web' => __DIR__.'/../routes/web.php',
    ];

    public function bootAddon()
    {
        // Boot logic here
    }
}

Important: Use bootAddon() instead of boot(). This ensures your addon boots after Statamic.

Step 4: Creating a Fieldtype

Let's create a custom fieldtype. Run:

php please make:fieldtype ColorPicker --addon=vendor/my-addon

This generates:

  • PHP fieldtype class
  • Vue component
  • Vite configuration

Your fieldtype class:

<?php

namespace Vendor\MyAddon\Fieldtypes;

use Statamic\Fields\Fieldtype;

class ColorPicker extends Fieldtype
{
    protected $icon = 'color-picker';

    protected static $title = 'Color Picker';

    public function component(): string
    {
        return 'color-picker';
    }

    public function configFieldItems(): array
    {
        return [
            'default' => [
                'display' => 'Default Color',
                'type' => 'text',
                'default' => '#000000',
                'width' => 50,
            ],
            'format' => [
                'display' => 'Color Format',
                'type' => 'select',
                'default' => 'hex',
                'options' => [
                    'hex' => 'Hex',
                    'rgb' => 'RGB',
                    'hsl' => 'HSL',
                ],
                'width' => 50,
            ],
        ];
    }
}

Step 5: Setting Up Vite

Your addon needs these files for asset compilation:

package.json

{
    "private": true,
    "scripts": {
        "dev": "vite",
        "build": "vite build"
    },
    "devDependencies": {
        "@vitejs/plugin-vue2": "^2.3.1",
        "laravel-vite-plugin": "^1.0",
        "vite": "^5.0"
    }
}

vite.config.js

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue2';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/js/cp.js',
                'resources/css/cp.css'
            ],
            publicDirectory: 'resources/dist',
        }),
        vue(),
    ],
});

resources/js/cp.js

import ColorPicker from './components/fieldtypes/ColorPicker.vue';

Statamic.booting(() => {
    Statamic.component('color-picker', ColorPicker);
});

Step 6: Creating Control Panel Routes

Create routes/cp.php:

<?php

use Illuminate\Support\Facades\Route;
use Vendor\MyAddon\Http\Controllers\CP\DashboardController;

Route::prefix('my-addon')->name('my-addon.')->group(function () {
    Route::get('/', [DashboardController::class, 'index'])->name('index');
    Route::get('/api/data', [DashboardController::class, 'data'])->name('data');
    Route::post('/api/save', [DashboardController::class, 'save'])->name('save');
});

Create your controller:

<?php

namespace Vendor\MyAddon\Http\Controllers\CP;

use Statamic\Http\Controllers\CP\CpController;
use Illuminate\Http\Request;

class DashboardController extends CpController
{
    public function index()
    {
        return view('my-addon::dashboard');
    }

    public function data()
    {
        return response()->json([
            'success' => true,
            'data' => [
                'stats' => $this->getStats(),
            ]
        ]);
    }

    public function save(Request $request)
    {
        $request->validate([
            'setting' => 'required|string',
        ]);

        // Save logic here

        return response()->json([
            'success' => true,
            'message' => 'Settings saved successfully'
        ]);
    }

    private function getStats()
    {
        return [
            'total' => 42,
            'active' => 30,
        ];
    }
}

Step 7: Adding Navigation

Add your addon to the Control Panel navigation in your service provider:

use Statamic\Facades\CP\Nav;

public function bootAddon()
{
    Nav::extend(function ($nav) {
        $nav->content('My Addon')
            ->section('Tools')
            ->route('my-addon.index')
            ->icon('addons')
            ->active('my-addon.*');
    });
}

Step 8: Component Registration

As of Statamic 5.28+, many components are autoloaded. If you're supporting older versions or want explicit control, register them manually:

protected $tags = [
    \Vendor\MyAddon\Tags\MyTag::class,
];

protected $fieldtypes = [
    \Vendor\MyAddon\Fieldtypes\ColorPicker::class,
];

protected $modifiers = [
    \Vendor\MyAddon\Modifiers\MyModifier::class,
];

protected $widgets = [
    \Vendor\MyAddon\Widgets\MyWidget::class,
];

Step 9: Publishing Assets

During development, run:

cd addons/vendor/my-addon
npm install
npm run dev

For production builds:

npm run build

Assets are automatically published when users run composer update.

Step 10: Adding Configuration

Create a config file that users can publish:

// In your service provider
protected $publishables = [
    __DIR__.'/../config/my-addon.php' => 'config/my-addon.php',
];

Create config/my-addon.php:

<?php

return [
    'api_key' => env('MY_ADDON_API_KEY'),
    'cache_ttl' => 3600,
    'features' => [
        'advanced_mode' => false,
    ],
];

Users can publish and customize:

php artisan vendor:publish --tag=my-addon

Testing Your Addon

Your addon comes with PHPUnit configured. Write tests in tests/:

<?php

namespace Vendor\MyAddon\Tests;

class ColorPickerTest extends TestCase
{
    /** @test */
    public function it_saves_color_value()
    {
        $field = new \Vendor\MyAddon\Fieldtypes\ColorPicker();
        $field->setField(new \Statamic\Fields\Field('color', [
            'type' => 'color_picker',
            'default' => '#ff0000',
        ]));

        $processed = $field->process('#00ff00');

        $this->assertEquals('#00ff00', $processed);
    }
}

Run tests:

./vendor/bin/phpunit

Directory Structure

Your complete addon structure:

my-addon/
├── config/
│   └── my-addon.php
├── resources/
│   ├── dist/
│   ├── js/
│   │   ├── components/
│   │   │   └── fieldtypes/
│   │   │       └── ColorPicker.vue
│   │   └── cp.js
│   ├── css/
│   │   └── cp.css
│   └── views/
│       └── dashboard.blade.php
├── routes/
│   ├── cp.php
│   └── web.php
├── src/
│   ├── Fieldtypes/
│   │   └── ColorPicker.php
│   ├── Http/
│   │   └── Controllers/
│   │       └── CP/
│   │           └── DashboardController.php
│   └── ServiceProvider.php
├── tests/
│   ├── ColorPickerTest.php
│   └── TestCase.php
├── composer.json
├── package.json
├── phpunit.xml
└── vite.config.js

Next Steps

  1. Add Features: Implement your addon's core functionality
  2. Write Documentation: Create clear usage instructions
  3. Add Tests: Cover critical paths with automated tests
  4. Consider Editions: Add free/pro tiers if selling commercially
  5. Publish: Share on the Statamic Marketplace

Troubleshooting

"Vite manifest not found"

Run npm install && npm run dev in your addon directory.

"Class not found"

Check your PSR-4 namespace in composer.json and run composer dump-autoload.

Assets not loading

Ensure your $vite property matches your vite.config.js.

Routes not working

Verify your routes are in the $routes property and files exist.

Publishing to the Marketplace

When ready to share:

  1. Publish to Packagist
  2. Create a seller account
  3. Submit your addon with description, pricing, and documentation
  4. Wait for review and approval

Remember: First edition should be the most limited (free tier), with additional editions offering more features.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *