novius/laravel-filament-publishable
Add Laravel Publishable support to Filament resources: ready-made form fields, table column, filter, and bulk action to manage publication status plus published/expired dates for your Eloquent models. Compatible with Laravel 11+, Filament 4+.
Add publishable fields to a Filament Resource form and table:
use Novius\FilamentPublishable\Fields\PublicationStatus;
use Novius\FilamentPublishable\Fields\PublishedAt;
use Novius\FilamentPublishable\Fields\ExpiredAt;
use Novius\FilamentPublishable\Columns\PublicationColumn;
use Novius\FilamentPublishable\Filters\PublicationStatusFilter;
public static function form(Form $form): Form
{
return $form->schema([
PublicationStatus::make('publication_status')
->required()
->default(PublicationStatus::DRAFT),
PublishedAt::make('published_at')
->requiredIf(fn ($record) => $record->publication_status === PublicationStatus::PUBLISHED),
ExpiredAt::make('expired_at')
->nullable(),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
PublicationColumn::make('publication_status')
->badge()
->color(fn ($state) => match ($state) {
PublicationStatus::DRAFT => 'gray',
PublicationStatus::PUBLISHED => 'green',
PublicationStatus::EXPIRED => 'red',
}),
])
->filters([
PublicationStatusFilter::make('publication_status'),
]);
}
Enable bulk publishing/expiring in the table:
use Novius\FilamentPublishable\Actions\PublicationBulkAction;
public static function table(Table $table): Table
{
return $table
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
PublicationBulkAction::make()
->label('Publish Selected')
->action('publish'),
PublicationBulkAction::make()
->label('Expire Selected')
->action('expire'),
]);
}
Override defaults (e.g., disable PublishedAt for drafts):
PublishedAt::make('published_at')
->disabled(fn ($record) => $record->publication_status === PublicationStatus::DRAFT)
->requiredIf(fn ($record) => $record->publication_status === PublicationStatus::PUBLISHED),
Sync Filament UI with model events (e.g., auto-expire after 30 days):
// In your model observer
public function expired(Post $post)
{
if ($post->expired_at && $post->expired_at->isPast()) {
$post->update(['publication_status' => PublicationStatus::EXPIRED]);
}
}
Show/hide fields based on publishable state:
public static function getFormSchema(): array
{
return [
PublicationStatus::make('status'),
PublishedAt::make('published_at')
->visible(fn ($record) => $record->status === PublicationStatus::PUBLISHED),
ExpiredAt::make('expired_at')
->visible(fn ($record) => $record->status === PublicationStatus::EXPIRED),
];
}
Add validation for publishable fields:
PublishedAt::make('published_at')
->rules([
'required_if:publication_status,' . PublicationStatus::PUBLISHED,
'after:now',
]),
Create a custom field (e.g., PublishableToggle):
use Filament\Forms\Components\Toggle;
class PublishableToggle extends Toggle
{
protected string $fieldName = 'publication_status';
public function mount(): void
{
$this->options([
PublicationStatus::DRAFT => 'Draft',
PublicationStatus::PUBLISHED => 'Published',
]);
}
}
Filter table data by publishable state:
public static function getTableQuery(): Builder
{
return parent::getTableQuery()
->where('publication_status', PublicationStatus::PUBLISHED)
->orWhereNull('published_at');
}
Customize field labels/translations:
// Publish lang file
'publication_status' => [
'labels' => [
'draft' => 'In Review',
'published' => 'Live',
'expired' => 'Archived',
],
],
// In your resource
PublicationStatus::make('status')
->label('Content Status'),
Add publishable-specific actions:
use Filament\Resources\Actions\Action;
public static function getActions(): array
{
return [
Action::make('publish')
->label('Publish Now')
->action(fn (Post $record) => $record->publish()),
];
}
Test field rendering and logic:
public function test_publication_status_field()
{
$field = PublicationStatus::make('status');
$field->fill();
$this->assertEquals(
PublicationStatus::DRAFT,
$field->getState()
);
}
Ensure Filament reflects model state changes:
// In your model
protected static function booted()
{
static::updated(function ($model) {
if ($model->isDirty('publication_status')) {
event(new PublicationStatusUpdated($model));
}
});
}
Handle soft-deleted publishable models:
public static function getTableQuery(): Builder
{
return parent::getTableQuery()
->withTrashed()
->whereNull('deleted_at')
->orWhere('publication_status', PublicationStatus::EXPIRED);
}
Expose publishable fields in API responses:
// In your API Resource
public function toArray($request)
{
return [
'status' => $this->publication_status,
'published_at' => $this->published_at?->toDateTimeString(),
];
}
Create widgets to track publishable content:
use Novius\FilamentPublishable\Widgets\PublishableStats;
public static function getWidgets(): array
{
return [
PublishableStats::make()
->model(Post::class)
->columns(3),
];
}
Extend the PublicationStatus enum:
enum PublicationStatus: string
{
case DRAFT = 'draft';
case REVIEW = 'review';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
}
Add confirmation dialogs for bulk actions:
PublicationBulkAction::make()
->requiresConfirmation()
->confirmationMessage('Are you sure you want to publish these items?'),
Make fields dependent on others:
ExpiredAt::make('expired_at')
->requiredIf(fn ($record) => $record->publication_status === PublicationStatus::PUBLISHED)
->after('published_at'),
Use across multiple Filament panels:
// In your FilamentServiceProvider
Filament::registerPanel(
default: CustomPanel::make()
->id('admin')
->resources([
PostResource::class,
// Other publishable resources
]),
);
Publishable trait.use Novius\Publishable\Publishable;
class Post extends Model
{
use Publishable;
}
PublishedAt/ExpiredAt may not respect publication_status defaults.default() in the field:
PublishedAt::make()
->default(now()->addDays(1)),
published_at/expired_at may not align with user timezone.PublishedAt::make()
->default(now()->setTimezone('UTC')),
PublicationBulkAction::make()
->validateUsing(fn (array $records) => collect($records)->every(fn ($record) =>
$record->publication_status === PublicationStatus::DRAFT
)),
How can I help you explore Laravel packages today?