The Laravel Filament Flexible Content Blocks package helps you to easily create content in Filament for any model, with predefined blocks, and foreach block an extendable Blade view component.
You can use this opinionated package to create a basic CMS, by setting up your own page model and implementing the predefined traits to select the functionality you need, then quickly setup a Filament resource by implementing the ready-made fields. Or you can add flexible content to a model for your specific business case, for instance to allow the flexible creation of a product description. Each project is different and to foster changing requirements, the focus is on the building blocks and not a default implementation for a CMS-like page.
The key goals of this package are:
- provide a quick way to add content to a model through reusable fields and content blocks
- quickly set up the frontend and allow different frontend stylings for each block
- have fully-working, extendable Blade view components with basic Tailwind styling
- allow the liberty to pick and choose which fields and blocks you need for your requirements
- provide easy configuration to override the behaviour of the fields, blocks and image conversions
- support SEO
- support overview fields to display the content in a list with custom title, image and description
- support content translations
- provide a start set of content blocks for most general requirements
The image conversions in the configuration file have changed due to a refactor of spatie-medialibrary v11. Check the upgrade guide for details.
If you need to upgrade to v1.0.0 it is IMPORTANT to read the upgrade guide thoroughly, because the data model of the content blocks has changed and needs to be upgraded via a command.
You can install the package via composer:
composer require statikbe/laravel-filament-flexible-content-blocks
You will most likely need to edit the extensive configuration, so you can publish the config file with:
php artisan vendor:publish --tag="filament-flexible-content-blocks-config"
Optionally, you can publish the views (e.g. if you want to tweak the content blocks) using:
php artisan vendor:publish --tag="filament-flexible-content-blocks-views"
Since you can apply the flexible content blocks to any view, we do not provide required or default migrations. However, we provide two example migrations, one for a translatable page and one for a single-language page. You can use these migrations as an example to create your own migrations. (see ToDo's) You can publish and run the migrations with:
php artisan vendor:publish --tag="filament-flexible-content-blocks-migrations"
#first edit the migrations and then run:
php artisan migrate
This is an opinionated package with batteries included. So we picked a set of dependencies to build upon. Here is a brief overview of the choices made:
filament/filament
: obviously ;-)spatie/laravel-medialibrary
&filament/spatie-laravel-media-library-plugin
: all image handling is done withspatie/medialibrary
spatie/laravel-sluggable
: for slugsspatie/laravel-translatable
: for translations as this works together with the first party filament translatable package.dereuromark/media-embed
: to support video embeds of various media services.openai-php/laravel
: to generate content and add other smart features ;-).
There is an example project of all the package features, which includes:
- migrations
- data models for default & translatable pages
- Filament resources & pages
- Http controllers
- views
- a data seeder to generate test pages
You can use this as a starting point to see how the package can be used for a CMS pages. Below we briefly discuss how to setup a migration, a model and a Filament resource in four steps. In the future, we want to add question-based commands to create these, see roadmap.
You can start from the example migrations that can be published. Then prune and pick the fields that fit
your requirements, note that some model traits need a combination of fields, e.g. the hero image needs both hero_image_copyright
and
hero_image_title
. The clusters are commented in the migration or have a look in the model traits to get an idea.
If you do not have a model yet, create one with php artisan make:model
, then you can add the interfaces and its default
implementation via traits. Below is an overview of the provided interfaces and traits and their functionality:
This adds a title and publishing begin and end date variables, together with functions and scopes to help with finding published models. Implement this interface with the trait HasPageAttributesTrait or HasTranslatedPageAttributesTrait for translated content.
This adds an introduction text variable. Implement this interface with the trait HasIntroAttributeTrait or HasTranslatedIntroAttributeTrait.
Adds a code variable to be able to select a specific content model in your source code by string instead of a varying id or slug. For instance, this is useful to look up a home page. Implement this with the trait HasCodeTrait.
Adds a hero image as well as a title (e.g. for accessibility in alt
tags) and copyright variable to the model. This can
be implemented with HasHeroImageAttributesTrait and
HasTranslatedHeroImageAttributesTrait.
Adds a JSON column to your model to store the data of the flexible content blocks. This is required if you want to use the flexible content blocks. Implement it with HasContentBlocksTrait or HasTranslatedContentBlocksTrait.
This provides some helper functions for handling media. You do not need to add traits, since the trait will be included by other traits that handle images.
If you want to support translatable media (e.g. a different image for each locale), include this interface. The implementation is done by the trait HasTranslatedAttributesTrait.php. The traits that implement translatable images, will make use of this implementation to keep track of the media that needs translation.
Overview fields can be used to display the content models as brief snippets in lists, for instance a list of news articles. Implement this with HasOverviewAttributesTrait or HasTranslatedOverviewAttributesTrait.
This adds a new title, description, image and keywords for SEO. It provides fallbacks to the regular title, intro and hero image if no SEO fields are completed. Implement the HasSEOAttributesTrait or the HasTranslatedSEOAttributesTrait for translatable content.
Add this interface if you want to use the model to link in call-to-actions to create dynamic URL's, or if you want to use the view action in the Filament table. The interface asks you to implement two functions one to get the url where the content can be publicly viewed and another to view the unpublished content. There is no default implementation trait, because this package is unaware of the used routes and controllers.
For slug support you can include the HasSlugAttributeTrait or the HasTranslatedSlugAttributeTrait for translatable slugs. If you use translatable slugs, please change the page routing the Filament Resource.
To support hierarchical content you can include the HasParent interface and the implementation is done by HasParentTrait.
If you want to implement a nested URL structure, check this documentation.
Create a filament resource and its page with the filament command.
There are few column fields provided by the package and a filter to select published/unpublished models. Below is an overview of all available columns in a code sample:
public static function table(Table $table): Table {
return $table->columns([
TitleColumn::create(),
PublishedColumn::create(),
])
->filters([
PublishedFilter::create(),
])
->actions([
Tables\Actions\EditAction::make(),
PublishAction::make(),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make(),
]);
}
If your model uses the Linkable
interface, you can also use the
provided ViewAction
in your table.
This action will simply open the url returned by the getViewUrl()
method on your model.
use Statikbe\FilamentFlexibleContentBlocks\Filament\Table\Actions\ViewAction;
->actions([
Tables\Actions\EditAction::make(),
PublishAction::make(),
ViewAction::make(), // <-- Add this
])
And then you can implement the form()
function with Filament fields provided by the package. Below is an example with tabs.
Note that we sometimes make use of create()
static functions, because we want to set the
names to the fixed variables used in the models. Some fields in the example are bundled in groupings
(e.g. SEOFields), you can also use the single fields if you
want make a custom layout.
public static function form(Form $form): Form {
return $form
->schema([
Tabs::make('Heading')
->columnSpan(2)
->tabs([
Tab::make('General')
->schema([
TitleField::create(true),
SlugField::create(),
PublicationSection::create(),
AuthorField::create(),
HeroImageSection::create(),
IntroField::create(),
ParentField::create(),
]),
Tab::make('Content')
->schema([
ContentBlocksField::create(),
]),
Tab::make('Overview')
->schema([
OverviewFields::make(1),
]),
Tab::make('SEO')
->schema([
SEOFields::make(1),
]),
]),
]);
}
If you have translatable content, you need to include the traits and language switch field of the filament/spatie-laravel-translatable-plugin
to
the resource
and its pages.
Important: On the edit page, you need to use the EditRecord\Concerns\TranslatableWithMedia trait if you use translatable images.
And on the create page use CreateRecord\Concerns\TranslatableWithMedia.
If you are using translated slugs on your model, it is possible that you create a new model instance without a translated slug.
When you then switch to the language without a translated slug, Filament uses the slug as the route binding key as
defined in the model class. This will result in an error since the slug translation does not yet exist. To solve this it is
easiest to use the ID as route binding key in Filament instead of the slug. You can do this by changing the page urls in
the Filament resource class, so that they do not try to resolve the object with /{record}/edit
, but use the ID attribute
/{record:id}/edit
. Then set the $recordRouteKeyName
of the Filament resource to id
.
protected static ?string $recordRouteKeyName = 'id';
public static function getPages(): array
{
//note: replace the page classes!
return [
'index' => Pages\ListArticles::route('/'),
'create' => Pages\CreateArticle::route('/create'),
'edit' => Pages\EditArticle::route('/{record:id}/edit'),
];
}
Now you need to create a controller and GET
route that returns a Blade view to display your content. See the example code for controller examples.
We provide Blade components for all fields (except SEO fields). Below is an example of a simple Blade template,
where the model is passed to this view as $page
variable. The <x-flexible-hero>
component renders a default hero with
title and full screen image, and the <x-flexible-content-blocks>
component renders all the content of each block.
Note: The x-flexible-hero
component requires Alpine.js.
<x-layouts.flexible title="{{ $page->title }}" wide="true">
<x-flexible-hero :page="$page" />
<div>
<x-flexible-content-blocks :page="$page">
</x-flexible-content-blocks>
</div>
</x-layouts.flexible>
If you want to customise these component views, you can publish the views.
You can use the SEO library of your preference to render the SEO tags on the page, e.g. with artesaos/seotools
.
We are dreaming up some exciting new AI-based features to ease content creation and translation. The first has arrived:
There is a form action SEOAIAction
that generates an SEO title, description and tags based on the model's content.
To enable this, you should complete the configuration of the OpenAI library. Do not forget to run the install command:
php artisan openai:install
Create an OpenAI API key and set this up in the .env
file of your project:
OPENAI_API_KEY=sk-...
OPENAI_ORGANIZATION=org-...
You will find the SEO AI action already set up in the SEOFields
group.
To build your content, the package provides the default blocks listed below.
A basic block with title and text.
Additionally to the text block, you can add an image with its title and copyright. The image can be positioned left or right of the text and an image conversion and background colour of the block can be set. One can also add a call-to-action button with configured button styles. You can link to URL's, but also dynamically to other models or routes.
This displays an image with copyright message and you can set image conversions, the position and the width the image should use on the page.
You can embed videos from numerous media services and set an overlay image that will cause the video embed to be lazy loaded after clicking the image.
A block to show a quote and it's author.
A block to insert custom HTML.
This block focuses on adding call-to-actions with image and text.
This block can be used to display the overview fields and image of other model records, e.g. for displaying related blog posts. One can configure the grid columns and background colour.
This block is comparable to the overview block, however you can add the title, description, image and CTA for each card. The image conversion, background colour and grid columns can be configured.
You can select Blade templates that you want to include. This can be handy to add small forms or interactive components, e.g. a newsletter signup form or a map.
You can easily create your own content block by extending AbstractContentBlock.
Note: Do NOT use AbstractFilamentFlexibleContentBlock.php to extend from, because this super class contains logic to add the package's own namespace to the custom block.
A lot of the customisation of content blocks and their behaviour can be done through the configuration file. Please, consult the configuration documentation.
Below is a list of ideas and missing features. PR's are welcome!
- Command to generate migrations
- Command to generate models
- Command to generate Filament resource and pages
Integrate image asset manager, see Filament Flexible Blocks Asset Manager- Store links to models in rich editor
- Redirects
- Reusable blocks. Name: global block?
- Focal point for image resizing
- Widget to show which records do not have a translated version
Please see CHANGELOG for more information on what has changed recently.
Please, submit bugs or feature requests via the Github issues. Pull requests are welcomed! Thanks!
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.