Writing an addon for Statamic (aka learning to love the AddonServiceProvider)

Published May 25th, 2022

Important: The information in this article is over 12 months old, and may be out of date or no longer relevant.

But hey, you're here anyway, so give it a read see if it still applies to you.

Statamic is an incredible foundation to bathe in awesomesauce. And even more so because it has been designed with extending its capabilities in mind through developer-built addons.

I’m hoping this article can help you get started writing your own addons too – and share some of my setup processes to help make development easier.

As always, Statamic’s documentation is awesome – so read up the Extending / Addons section.

If you want some code samples, check out addons I’ve written, including Iconamic, Feedamic and Sitemapamic – you’ll see some working examples of fieldtypes, routes, views, config and tags. The best part of open source is learning from others – and I’m always up for a chat if you need some help.

Private or public Statamic addons? 

The very first question to ask yourself is where this addon will be used (and by who). Statamic offers two types: private and public addons.

A private addon will have its code living with the rest of your project, and cannot be installed by others. A public addon lives in its own repository and can be installed by others – either your own private repo or via composer and packagist.org.

Pick a private addon if:

  • it will do incredibly project-specific features

  • you’re happy to have the source code as part of your core project

  • you have no interest in distributing/sharing it

Pick a public addon if:

  • you want to distribute it (either free or paid) on the Statamic Marketplace

  • it has use cases beyond just your current project

  • you’re extending Statamic with something other developers may use too

I’ve written both types – and in all times where I started with a private addon, it eventually became a public one.

Fun fact: Iconamic started its life as a much simpler icon picker fieldtype for a single project. But we kept having the need in other projects, so re-wrote it as a public addon, and now offer it for free for other developers to use too.

With me so far? What type of addon do you think you’ll be building? Just remember there’s no right or wrong answer – both have valid use cases. It’s entirely up to you, your project and your future plans as to whether you go public or private.

Setting up for local development 

A private addon

If you go private, you can simply create your addon using Statamic’s please command.

1php please make:addon example/my-addon
Copied!

If you’ve been reading the docs, that’ll look familiar. Just follow the steps and away you go.

A public addon

If I’m creating a public addon, I like to move the source out of my project early on, just to help keep things separate (and make it easier to re-install it in a test project to make sure it all works in a different scope too).

This is an opinionated approach, but one that just sets me up for a happy development time.

First of all, using the same please command, I make my addon.

1php please make:addon martyfriedel/mcfly
Copied!

The boilerplate is all set, but the only issue is, it lives in my project’s source. Given it will eventually have its own repo, I’ll move it out now. To do this, I:

Move the source 

Manually move the martyfriedel/mcfly folder from my project’s addons directory to where I want it to live on my local machine (I have a folder set up just for all of my Statamic addons)

Update composer.json

The please command added a repositories section to our composer.json file. All we need to do is update the url to point to the new home (i.e. where you moved it above). Make this URL be relative from the project root:

1"repositories": [
2 {
3 "type": "path",
4 "url": "../statamic-addons/martyfriedel/mcfly"
5 }
6]
Copied!

Update composer

Just run:

1composer update martyfriedel/mcfly
Copied!

Your addon will now be symlinked to your addon’s new code home.

With this done, we are ready to get started.

What I love about this approach is that I can now keep one project open, but tweak my addon via the vendor folder knowing it is symlinked to the source directory. I know that sounds so contradictory (you never ever change vendor files, right?), but the symlink makes it OK as we’re not actually changing the vendor files. Not in that bad way at least. For development purposes, this makes life so much easier.

A quick look at your addon structure 

In your addon source you’ll see a README.md file – so don’t forget to write some good documentation. Good documentation always helps.

And you’ll also see a src folder – this is where your PHP code lives, and starts with the ServiceProvider which boots your addon.

I like to organise my different components neatly, following the approach of the app folder in a Laravel project – for example, Http/Controllers for any controller logic, Tags for Statamic Tags, Commands for console commands, and so on.

Start organising your code well from the start and it’ll make your long term life easier. 

The Service Provider (and doing things the right way)

Your addon has a Service Provider which tells Statamic and Laravel what to do, when, and from where.

This can include things like defining your config file, field types, routes, scripts, widgets, publishables, scopes, adding nav, tags, phew, heaps.

The best way to see exactly how all of this works is to explore your own service provider and its parent AddonServiceProvider.

Previously, we would use the boot method, however recent changes have recommended that we use the bootAddon method instead.

This actually does a lot of the heavy lifting for you when it comes to publishing different components, like views or config, as the different ‘boot’ methods, such as bootConfig will look for your config file in a specific (and predictably best practice) location.

When we get started, we’ll have a very empty Service Provider:

1namespace MartyFriedel\McFly;
2 
3use Statamic\Providers\AddonServiceProvider;
4 
5class ServiceProvider extends AddonServiceProvider
6{
7 public function bootAddon()
8 {
9 //
10 }
11}
Copied!

Ultimately what this means is you:

  1. end up writing less code in your service provider, and

  2. follow Statamic’s consistent and recommended structure

And those two things are great.

The scale of all of the different ‘boot’ methods is so powerful, and I can’t go through them all with you. But get code diving to see what each does, and which are applicable to your addon, and how they can help streamline your addon development.

Config

The bootConfig is one of the boot methods that is great to take advantage of.

This will look for a config file in your addon’s config directory that matches your addon’s name, and if found, will allow it to be published using Laravel’s vendor:publish command. 

Just to explain this another way:

  1. create a config folder in your addon

  2. create a file the same name as your addon, i.e. mcfly.php

  3. place your config options – just like any other Laravel/Statamic config array – in the file

  4. Your addon will know the config file exists, use it, and allow it to be published.

While it seems magic by having absolutely no reference to config within your service provider, having this little weight lifter behind the scenes helps create a consistent codebase across multiple addons.

To give your addon a config file, simply place it in the config/[addon-name].php file, and Statamic’s AddonServiceProvider will know what to do from there.

Fieldtype

Including a new fieldtype in your addon can be great. Statamic’s initial collection of fieldtypes gives so much flexibility, but there may be times when you need to add your own.

There’s a few things your fieldtype needs:

  • A PHP file to help configure and process values

  • A Vue file for the Statamic CP to use to render the fieldtype

  • A build process

This is another reason I like to have a separate repo for my addons – it means that I can build my addon independently of the project its in.

Before we go any further: have you read the Fieldtype docs yet?

To get started, in your main project, make a new fieldtype:

1php please make:fieldtype FluxCapacitor
Copied!

Wait, why in the main project? So this means Statamic is generating boilerplate code for you from whatever the latest templates are. So rather than copy and paste from old Statamic code (or anything I write here), run this command in your project and you’ll get the files you need. 

This will create two files that you need to move to your actual addon:

  1. app/Fieldtypes/FluxCapacitor.php moves to src/Fieldtypes/FluxCapacitor.php (and don’t forget to update your namespace)

  2. resources/js/components/fieldtypes/FluxCapacitor.vue moves to resources/js/fieldtypes/FluxCapacitor.vue

Don’t forget to remove these files from your main project, and also remember to update the namespace for the Fieldtype in your addon.

To build this fieldtype, we’ll need a build script. Personally, I love Laravel Mix so will install that via npm in the addon source – but if you like something else, you do it your way.

If you’re going down the Laravel Mix path, you’ll need to:

  1. Install Laravel Mix via npm

  2. Create a webpack.mix.js file

Statamic uses Vue 2, so your webpack.mix.js file will need to explicitly specify that:

1let mix = require('laravel-mix');
2 
3mix.js('resources/js/index.js', 'dist/js/mcfly.js').vue({version: 2});
Copied!

You may have noticed that we’re setting our entry point to be index.js which we haven’t created yet. So we probably should, right? 

This acts as a starting point for the build process and can do whatever your fieldtype (and addon) needs. 

For this example, all we need to do is register the FluxCapacitor fieldtype:

1import FluxCapacitorField from './fieldtypes/FluxCapacitor.vue';
2 
3Statamic.booting(() => {
4    Statamic.$components.register('flux_capacitor-fieldtype', FluxCapacitorField);
5});
Copied!

We’re now ready to build. This will create a dist/js/mcfly.js file for us which will register our new Flux Capacitor field type.

You’ll notice that underscore too – that’s to separate capital letters for Statamic. If you had a single-word Fieldtype, it would just be [name]-fieldtype.

Now that we have our basic Fieldtype PHP file and built js, we need to update our ServiceProvider so that it knows about both of these things. We can set the $fieldtypes and $scripts properties, and Statamic will do the rest.

1use MartyFriedel\McFly\Fieldtypes\FluxCapacitor;
2use Statamic\Providers\AddonServiceProvider;
3 
4class ServiceProvider extends AddonServiceProvider
5{
6    protected $fieldtypes = [
7        FluxCapacitor::class
8    ];
9 
10    protected $scripts = [
11        __DIR__.'/../dist/js/mcfly.js'
12    ];
13 
14    public function bootAddon()
15    {
16        //
17    }
18}
Copied!

Update your addon via composer, and you should see your JS file being published.

In your Statamic control panel, go to a Blueprint and add a new filed – you’ll see the new Flux Capacitor field type.

The Flux Capacitor Fieldtype in the Blueprint editor

To create a field type, you’ll need a PHP file, a Vue file and a build step.

Accessing assets during your build

When you make changes to your fieldtype, you’ll also need to ensure they get correctly published in your Statamic site too.

During dev, you really don’t want to have to run composer update every time (you can if you want though) so I like to set up a symlink back to the addon’s source dist directory.

1ln -s /path/to/mcfly/dist public/vendor/mcfly
Copied!

Not what you expect?

You may need to remove the public/vendor/mcfly directory first.

When this is set up, any time your built Javascript updates, your Statamic site will see it without any further steps. This is such a huge timesaver when making your fieldtype.

Customising your Fieldtype

Within your FluxCapacitor fieldtype PHP class, you’re able to extend and customise its behaviour and appearance.

You can set the protected $icon variable to be the name of one of Statamic’s icons, or the SVG content of your own.

You can set the protected $categories variable to have your Fieldtype appear in different categories in the list of fieldtypes. By default, it will just be under “Special” but maybe it fits under Structured or Media too. Just be responsible with your category selections: we don’t need it to appear everywhere.

You can also add your own configFieldItems function to return an array of config field types that will appear when someone selects your fieldtype.

1class FluxCapacitor extends Fieldtype
2{
3    protected $icon = '<svg>...</svg>';
4 
5    protected $categories = ['media', 'special'];
6 
7    public function __construct()
8    {
9        $this->configFields = [
10            'destination' => [
11                'display' => 'Where do you want to go?',
12                'type'    => 'select',
13                'default' => 'future',
14                'options' => [
15                    'future' => 'Future',
16                    'past'   => 'Past',
17                ],
18                'width'   => 33
19            ],
20        ];
21    }
22 
23    // ...
24}
Copied!

That config looks really familiar, right? If you’ve seen the raw blueprint files, these options will feel very similar.

To really understand what can be done, dive through the Statamic Fieldtypes source – Statamic has so much on offer, and more than the docs and articles like this can explain, so browse at your leisure and you’ll be able to build some really cool addons for Statamic too.

Passing Fieldtype config to your Vue component

Being able to set config options for each fieldtype instance is awesome. It gives you the ability to have each fieldtype instance be unique for its usage case – and it is really easy to pass these config options on to your Vue component.

Within your Fieldtype PHP class, you can use the preload method to pass information to your Vue component. Anything you return from this function will passed to the meta prop within your component.

1public function preload() {
2    return [
3        'destination' => $this->config('destination', 'future')
4    ];
5}
Copied!

Before you do your return, you could perform more function too – in Iconamic we get a list of icons from the configured directory for that fieldtype. Perhaps you want to do some sort of database or third party lookup. Basically this is the place to collect data to send across to your component.

In your component you can build and define whatever structure you need to get your Fieldtype to do its thing. Just note that any styles you use should be part of your component: if you try to rely on Tailwind, your classes may not be part of Statamic’s build, or may behave differently to your addon’s configuration. You can overcome this by using the @apply directive (so that your build script relies on your configuration), or just writing your own CSS within your component.

What you do in your component is totally up to you – work your magic!

Complex fieldtypes with multiple inputs

Each Fieldtype has a single value – and one that shouldn’t be updated directly. We should always use the update method of the Fieldtype mixin so that the internal stores are updated correctly.

But what if you need a more complex data store?

Just take one step further – keep a simple Fieldtype component, and have the model set on a child component that does have a more detailed/structured data requirement:

1<advanced-flux v-model="value" @input="update"></advanced-flux>
Copied!

So your component’s value is passed to the child, which can make changes to its structure, and then update back again.

Whatever the advanced-flux component does is up to you. You just need to make sure that your advanced-flux component emits an input event, and that in turn will update Statamic’s internal stores.

The Flux Capacitor example

OK, so what does an entire component look like with config from the PHP Fieldtype and CSS? Pretty straight forward actually:

1<template>
2 
3 <div>
4 <text-input :value="value" @input="update"/>
5 <p>You will go back to the <strong>{{ this.meta.destination }}</strong>.</p>
6 </div>
7 
8</template>
9 
10<script>
11export default {
12 mixins: [Fieldtype]
13};
14</script>
15<style scoped>
16p {
17 margin-top: 0.125rem;
18 font-size: 0.75rem;
19}
20</style>
Copied!

 And when we’re authoring with the field, we’ll see:

The Flux Capacitor Field, with config, in Statamic

Tags 

Tags are a brilliant way to add additional functionality to the Statamic templating engine – and can be a great companion when working with a custom fieldtype especially when you need to do some custom processing on more complex data types.

Writing a Tag is really straight forward and can have so much power and flexibility. Guess what… yep, you guessed it, check out the Tag docs for full details.

Make sure when building a tag that you test it with both the regex and runtime engines.

When your tags are ready to go, you can add them to the $tags property of the Service Provider, and you’ll be all set to use them in your Antlers files:

1use MartyFriedel\McFly\Fieldtypes\FluxCapacitor as FluxCapacitorFieldtype;
2use MartyFriedel\McFly\Tags\FluxCapacitor as FluxCapacitorTag;
3use Statamic\Providers\AddonServiceProvider;
4 
5class ServiceProvider extends AddonServiceProvider
6{
7    protected $fieldtypes = [
8        FluxCapacitorFieldtype::class
9    ];
10 
11    protected $scripts = [
12        __DIR__.'/../dist/js/mcfly.js'
13    ];
14 
15    protected $tags = [
16        FluxCapacitorTag::class
17    ];
18 
19    public function bootAddon()
20    {
21        //
22    }
23}
Copied!

Notice here too I’ve changed the use statements to have an alias given they have the same name – this is just to help with readability of the actual properties.

Routes 

Your addon may need its own routes – maybe public like in Sitemapamic or maybe for the CP like in the Tiny Cloud addon.

If you’re familiar with routes in Laravel, this will be a walk in the park.

And because writing an addon is so simple, all you need to do is tell your Service Provider by updating the $routes property:

1protected $routes = [
2    // for the control panel
3    'cp' => __DIR__.'/../routes/cp.php',
4 
5    // for the public routes
6    'web' => __DIR__.'/../routes/web.php'
7];
Copied!

We use web for the public routes – just like you would in a Laravel app – and use cp for any Statamic Control Panel routes that your addon may use. You can also use actions for front end action endpoints (like the action when we submit a form in vanilla Statamic). Of course the Addon Docs on Routing are really useful to read.

When you’ve defined your routes this way, the bootRoutes method will do the rest for you.

Views 

The bootViews method is another helper from the boot chain of methods that automatically picks up your views if you’ve stored them in the resources/views folder of your addon.

This means that there’s not a single thing to do to your Service Provider: simply place your views in the resources/views folder, and Statamic’s AddonServiceProvider will do the rest.

If you want to give the user the ability to publish the views (and make changes) you will need to do this yourself – this can go in the bootAddon method of your Service Provider:

1public function bootAddon()
2{
3    $this->publishes([
4        __DIR__.'/../resources/views' => resource_path('views/vendor/martyfriedel/mcfly'),
5    ], 'mcfly-views');
6}
Copied!

While the AddonServiceProvider does have a $publishables property, it is designed for public assets (like images), not views. Fun fact, there’s also a $stylesheets (and $scripts, but we've already talked about that) property for any specific CSS you need to publish too.

Listen

Are you a good listener? It’s easy for your Addon to become a good listener.

I used listening to events in Sitemapamic to listen for specific events when an Entry or Term changed. Long story short: Sitemapamic caches the sitemap forever, or until you manually invalidate it or an Entry or Term is saved or deleted. By listening for these events, it is possible to perform actions within your addon effortlessly.

Just like Laravel’s event/listener pairs, the $listen property in your Service Provider:

1protected $listen = [
2    \Statamic\Events\EntrySaved::class => [
3        \MartyFriedel\McFly\Listeners\SendMartyBackToTheFuture::class,
4    ]
5]
Copied!

When the EntrySaved event is fired, the McFly addon’s SendMartyBackToTheFuture listener will kick in.

So what can you listen for? As of this writing, 50 different events. Check out Statamic’s Events docs for more details.

When something is saved, you could clear a cache. When a user is registered, you could kick start some other process with a CRM or other system. When a form is submitted, you could ping off a Slack notification. So much freedom here to make your addon really excel.

The $listen property will get picked up as part of the addon’s boot sequence, and automatically applied.

So what? 

Writing addons rocks, that’s what. And the different boot helpers make it really easy to follow best practices and write less code.

Take Iconamic for example: it has a publishable config file, custom fieldtype, a tag, and an update script. Yet it’s ServiceProvider is tiny:

1class ServiceProvider extends AddonServiceProvider
2{
3    protected $fieldtypes = [
4        IconamicFieldtype::class
5    ];
6 
7    protected $scripts = [
8        __DIR__.'/../dist/js/iconamic.js'
9    ];
10 
11    protected $tags = [
12        IconamicTag::class
13    ];
14 
15    protected $updateScripts = [
16        // v1.0.2
17        MoveConfigFile::class
18    ];
19}
Copied!

By taking advantage of the AddonServiceProvider, and storing files in logical, common and expected places, your addon development process can be simple and predictable. 

When getting started, go beyond a ‘hello, world’ and having something “just work”, but explore the AddonServiceProvider source code, and explore what others have done, to get a good understanding of how these helpers have been built to do just that: help.

It will lead you to creating consistent code that fits so well within the Laravel and Statamic ecosystem, and that other Laravel/Statamic developers can look at and easily understand and find what they’re looking for.

And this is just the start… while this is a really high level setup and look at a few key areas, there’s so much more to uncover when building an addon for Statamic. The Addon documentation is an incredible place to start, and feel free to explore the source of the addons I’ve written too.

For more advanced addons there’s still middleware, update scripts and Statamic nav to explore too! But you need to walk before you can run, so I hope you’ve found this crash course in to setting up an addon environment and a quick walkthrough some of the AddonServiceProvider’s features and helpers.

You may be interested in...