Plugin development
Overview
The basis of Filament plugins are Laravel packages. They are installed into your Filament project via Composer, and follow all the standard techniques, like using service providers to register routes, views, and translations. If you're new to Laravel package development, here are some resources that can help you grasp the core concepts:
- The Package Development section of the Laravel docs serves as a great reference guide.
- Spatie's Package Training course is a good instructional video series to teach you the process step by step.
- Spatie's Package Tools allows you to simplify your service provider classes using a fluent configuration object.
Filament plugins build on top of the concepts of Laravel packages and allow you to ship and consume reusable features for any Filament panel. They can be added to each panel one at a time, and are also configurable differently per-panel.
Configuring the panel with a plugin class
A plugin class is used to allow your package to interact with a panel configuration file. It's a simple PHP class that implements the Plugin
interface. 3 methods are required:
- The
getId()
method returns the unique identifier of the plugin amongst other plugins. Please ensure that it is specific enough to not clash with other plugins that might be used in the same project. - The
register()
method allows you to use any configuration option that is available to the panel. This includes registering resources, custom pages, themes, render hooks and more. - The
boot()
method is run only when the panel that the plugin is being registered to is actually in-use. It is executed by a middleware class.
<?php
namespace DanHarrin\FilamentBlog;
use DanHarrin\FilamentBlog\Pages\Settings;
use DanHarrin\FilamentBlog\Resources\CategoryResource;
use DanHarrin\FilamentBlog\Resources\PostResource;
use Filament\Contracts\Plugin;
use Filament\Panel;
class BlogPlugin implements Plugin
{
public function getId(): string
{
return 'blog';
}
public function register(Panel $panel): void
{
$panel
->resources([
PostResource::class,
CategoryResource::class,
])
->pages([
Settings::class,
]);
}
public function boot(Panel $panel): void
{
//
}
}
The users of your plugin can add it to a panel by instantiating the plugin class and passing it to the plugin()
method of the configuration:
use DanHarrin\FilamentBlog\BlogPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(new BlogPlugin());
}
Fluently instantiating the plugin class
You may want to add a make()
method to your plugin class to provide a fluent interface for your users to instantiate it. In addition, by using the container (app()
) to instantiate the plugin object, it can be replaced with a different implementation at runtime:
use Filament\Contracts\Plugin;
class BlogPlugin implements Plugin
{
public static function make(): static
{
return app(static::class);
}
// ...
}
Now, your users can use the make()
method:
use DanHarrin\FilamentBlog\BlogPlugin;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(BlogPlugin::make());
}
Configuring plugins per-panel
You may add other methods to your plugin class, which allow your users to configure it. We suggest that you add both a setter and a getter method for each option you provide. You should use a property to store the preference in the setter and retrieve it again in the getter:
use DanHarrin\FilamentBlog\Resources\AuthorResource;
use Filament\Contracts\Plugin;
use Filament\Panel;
class BlogPlugin implements Plugin
{
protected bool $hasAuthorResource = false;
public function authorResource(bool $condition = true): static
{
// This is the setter method, where the user's preference is
// stored in a property on the plugin object.
$this->hasAuthorResource = $condition;
// The plugin object is returned from the setter method to
// allow fluent chaining of configuration options.
return $this;
}
public function hasAuthorResource(): bool
{
// This is the getter method, where the user's preference
// is retrieved from the plugin property.
return $this->hasAuthorResource;
}
public function register(Panel $panel): void
{
// Since the `register()` method is executed after the user
// configures the plugin, you can access any of their
// preferences inside it.
if ($this->hasAuthorResource()) {
// Here, we only register the author resource on the
// panel if the user has requested it.
$panel->resources([
AuthorResource::class,
]);
}
}
// ...
}
Additionally, you can use the unique ID of the plugin to access any of its configuration options from outside the plugin class. To do this, pass the ID to the filament()
method:
filament('blog')->hasAuthorResource()
You may wish to have better type safety and IDE autocompletion when accessing configuration. It's completely up to you how you choose to achieve this, but one idea could be adding a static method to the plugin class to retrieve it:
use Filament\Contracts\Plugin;
class BlogPlugin implements Plugin
{
public static function get(): static
{
return filament(app(static::class)->getId());
}
// ...
}
Now, you can access the plugin configuration using the new static method:
BlogPlugin::get()->hasAuthorResource()
Distributing a panel in a plugin
It's very easy to distribute an entire panel in a Laravel package. This way, a user can simply install your plugin and have an entirely new part of their app pre-built.
When configuring a panel, the configuration class extends the PanelProvider
class, and that is a standard Laravel service provider. You can use it as a service provider in your package:
<?php
namespace DanHarrin\FilamentBlog;
use Filament\Panel;
use Filament\PanelProvider;
class BlogPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('blog')
->path('blog')
->resources([
// ...
])
->pages([
// ...
])
->widgets([
// ...
])
->middleware([
// ...
])
->authMiddleware([
// ...
]);
}
}
You should then register it as a service provider in the composer.json
of your package:
"extra": {
"laravel": {
"providers": [
"DanHarrin\\FilamentBlog\\BlogPanelProvider"
]
}
}