filament-pages-development
Build custom blocks and layouts for the filament-pages content management system.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Build custom blocks and layouts for the filament-pages content management system.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | filament-pages-development |
| description | Build custom blocks and layouts for the filament-pages content management system. |
Activate when working with:
#[IsBlock])#[IsLayout])Filament Pages uses attribute-driven discovery to find blocks and layouts at runtime.
#[IsBlock(type: '...', label: '...')] and implement PageBlock (or extend AbstractBlock). They define a Filament form schema, render HTML, and optionally declare assets and structured data.#[IsLayout(key: '...', label: '...')] and implement PageLayout (or extend AbstractLayout). They wrap the full page response with a Blade template.spatie/laravel-structure-discoverer to scan configured directories for annotated classes.Page model stores blocks as a JSON array of {type, data} objects, renders them via the block map, and collects assets into a BlockAssetBag.php artisan filament-pages:make-block Hero --no-interaction
<?php
declare(strict_types=1);
namespace App\Blocks;
use Bambamboole\FilamentPages\Blocks\AbstractBlock;
use Bambamboole\FilamentPages\Blocks\BlockAsset;
use Bambamboole\FilamentPages\Blocks\IsBlock;
use Bambamboole\FilamentPages\Models\Page;
use Filament\Forms\Components\Builder\Block;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\TextInput;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\View\View;
use RalphJSmit\Laravel\SEO\SchemaCollection;
#[IsBlock(type: 'hero', label: 'Hero Section')]
class HeroBlock extends AbstractBlock
{
// The Blade view rendered by the default render() method.
protected string $view = 'blocks.hero';
/**
* Define the Filament form fields shown in the admin panel.
* Receives a pre-configured Block builder and must return it.
*/
public function build(Block $block): Block
{
return $block
->icon(Heroicon::OutlinedStar)
->schema([
TextInput::make('heading')
->label('Heading')
->required(),
RichEditor::make('body')
->label('Body')
->columnSpanFull(),
]);
}
/**
* Override to customise rendering logic.
* The default implementation simply returns view($this->view, $data).
*/
#[\Override]
public function render(array $data, Page $page): View
{
return view($this->view, [
'heading' => $data['heading'] ?? '',
'body' => $data['body'] ?? '',
'page' => $page,
]);
}
/**
* Declare CSS and JS assets required by this block.
* Assets are deduplicated even when the block appears multiple times.
*
* @return array{css: list<BlockAsset>, js: list<BlockAsset>}
*/
#[\Override]
public function assets(): array
{
return [
'css' => [
BlockAsset::url('/css/hero.css'),
BlockAsset::inline('.hero { text-align: center; }'),
],
'js' => [
BlockAsset::url('/js/hero.js'),
],
];
}
/**
* Contribute JSON-LD structured data for SEO.
* Only add to the SchemaCollection when relevant; if nothing is added,
* the package skips schema output for this block.
*/
#[\Override]
public function registerSchema(SchemaCollection $schema, array $data, Page $page): SchemaCollection
{
return $schema;
}
}
Block views receive the $data array keys as variables (or whatever you pass from render()). Place them in resources/views/blocks/:
{{-- resources/views/blocks/hero.blade.php --}}
<section class="hero">
<h2>{{ $heading }}</h2>
<div>{!! $body !!}</div>
</section>
#[IsBlock(
type: 'hero', // Unique block type key stored in the JSON column
label: 'Hero Section', // Human-readable label (auto-generated from type if omitted)
translateLabel: false, // When true, the label is passed through __()
)]
php artisan filament-pages:make-layout Blog --no-interaction
<?php
declare(strict_types=1);
namespace App\Layouts;
use Bambamboole\FilamentPages\Layouts\AbstractLayout;
use Bambamboole\FilamentPages\Layouts\IsLayout;
use Bambamboole\FilamentPages\Models\Page;
use Illuminate\Http\Request;
use Illuminate\View\View;
#[IsLayout(key: 'blog', label: 'Blog')]
class BlogLayout extends AbstractLayout
{
protected string $view = 'layouts.blog';
#[\Override]
public function render(Request $request, Page $page): View
{
return view($this->view, ['page' => $page]);
}
}
Layout templates must call specific Blade directives to render block assets and content. Important: call $page->renderBlocks() before the style/script directives so assets are registered first.
{{-- resources/views/layouts/blog.blade.php --}}
@php $renderedBlocks = $page->renderBlocks(); @endphp
<!DOCTYPE html>
<html lang="{{ $page->locale ?? app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
{!! seo($page) !!}
@filamentPagesStyles
@filamentPagesBlockStyles
</head>
<body>
<main>
<h1>{{ $page->title }}</h1>
{!! $renderedBlocks !!}
</main>
@filamentPagesBlockScripts
</body>
</html>
| Directive | Purpose |
|---|---|
@filamentPagesStyles | Injects the package's base frontend CSS |
@filamentPagesBlockStyles | Renders <style> and <link> tags collected from block assets() |
@filamentPagesBlockScripts | Renders <script> tags collected from block assets() |
#[IsLayout(
key: 'blog', // Unique key stored on the Page model's layout column
label: 'Blog', // Human-readable label (auto-generated from key if omitted)
translateLabel: false, // When true, the label is passed through __()
)]
The BlockAsset value object represents a single CSS or JS asset. The BlockAssetBag collects and deduplicates assets across all blocks on a page.
use Bambamboole\FilamentPages\Blocks\BlockAsset;
// External file — rendered as <link> or <script src="...">
BlockAsset::url('/vendor/prism/prism.css');
// Inline content — rendered as <style>...</style> or <script>...</script>
BlockAsset::inline('.highlight { background: yellow; }');
public function assets(): array
{
return [
'css' => [
BlockAsset::url('/vendor/prism/prism.css'),
BlockAsset::inline('.styled { color: red; }'),
],
'js' => [
BlockAsset::url('/vendor/prism/prism.js'),
BlockAsset::inline('console.log("init")'),
],
];
}
BlockAssetBag automatically adds a nonce attribute to inline <style> and <script> tags when Vite::cspNonce() returns a value. No manual configuration needed.
Publish the config file with php artisan vendor:publish --tag=filament-pages-config.
Key settings in config/filament-pages.php:
return [
// Eloquent model used for pages (extend to customise)
'model' => \Bambamboole\FilamentPages\Models\Page::class,
// Include built-in Markdown and Image blocks in discovery
'load_default_blocks' => true,
// Directories scanned for #[IsLayout] classes
'layout_discovery_paths' => [
app_path('Layouts'),
],
// Directories scanned for #[IsBlock] classes
'block_discovery_paths' => [
app_path('Blocks'),
],
];
namespace Bambamboole\FilamentPages\Blocks;
interface PageBlock
{
public function build(Block $block): Block;
/** @param array<string, mixed> $data */
public function registerSchema(SchemaCollection $schema, array $data, Page $page): SchemaCollection;
/** @param array<string, mixed> $data */
public function render(array $data, Page $page): View;
/** @return array{css: list<BlockAsset>, js: list<BlockAsset>} */
public function assets(): array;
}
namespace Bambamboole\FilamentPages\Layouts;
interface PageLayout
{
public function render(Request $request, Page $page): View;
}
Extend AbstractBlock to get sensible defaults — only build() is abstract:
| Method | Default behaviour |
|---|---|
render() | Returns view($this->view, $data) |
registerSchema() | Returns $schema unchanged (no markup added) |
assets() | Returns ['css' => [], 'js' => []] (no assets) |
Extend AbstractLayout to get a default render() that returns view($this->view, ['page' => $page]).
Use FilamentPages::setBlockClasses() and FilamentPages::setLayoutClasses() to override discovery in tests, so only the blocks/layouts you specify are active.
use Bambamboole\FilamentPages\Facades\FilamentPages;
beforeEach(function () {
FilamentPages::setBlockClasses([
\App\Blocks\HeroBlock::class,
]);
});
afterEach(function () {
// Reset to re-enable automatic discovery
FilamentPages::setBlockClasses(null);
});
For layout testing:
FilamentPages::setLayoutClasses([
\App\Layouts\BlogLayout::class,
]);
// Reset after test
FilamentPages::setLayoutClasses(null);
Create test-only block classes in tests/Fixtures/ with the #[IsBlock] attribute. These can be injected via setBlockClasses() without polluting the application's block discovery paths.
// tests/Fixtures/StyledBlock.php
namespace Tests\Fixtures;
use Bambamboole\FilamentPages\Blocks\AbstractBlock;
use Bambamboole\FilamentPages\Blocks\BlockAsset;
use Bambamboole\FilamentPages\Blocks\IsBlock;
#[IsBlock(type: 'styled', label: 'Styled')]
class StyledBlock extends AbstractBlock
{
public function build(Block $block): Block
{
return $block->schema([
TextInput::make('content'),
]);
}
#[\Override]
public function assets(): array
{
return [
'css' => [BlockAsset::url('/vendor/prism/prism.css')],
'js' => [BlockAsset::inline('console.log("styled")')],
];
}
}