Block Development
This guide covers creating custom blocks for the TallCMS Rich Editor. Blocks are reusable content components that content editors can insert into pages and p...
Block Development Guide
Quick Start
Generate a new block using the Artisan command:
php artisan make:tallcms-block "product showcase"
This creates:
- Block class:
app/Filament/Forms/Components/RichEditor/RichContentCustomBlocks/ProductShowcaseBlock.php - Template:
resources/views/cms/blocks/product-showcase.blade.php
The block is auto-discovered and immediately available in the Rich Editor.
Block Architecture
File Structure
app/Filament/Forms/Components/RichEditor/RichContentCustomBlocks/
└── MyCustomBlock.php # Block class with form schema and rendering
resources/views/cms/blocks/
└── my-custom.blade.php # Blade template for frontend display
Block Class Anatomy
<?php
namespace App\Filament\Forms\Components\RichEditor\RichContentCustomBlocks;
use Filament\Actions\Action;
use Filament\Forms\Components\RichEditor\RichContentCustomBlock;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use TallCms\Cms\Filament\Blocks\Concerns\HasBlockMetadata;
class ProductShowcaseBlock extends RichContentCustomBlock
{
use HasBlockMetadata;
// Required: Unique identifier
public static function getId(): string
{
return 'product_showcase';
}
// Required: Display name in block panel
public static function getLabel(): string
{
return 'Product Showcase';
}
// Block panel metadata
public static function getCategory(): string
{
return 'content';
}
public static function getIcon(): string
{
return 'heroicon-o-shopping-bag';
}
public static function getDescription(): string
{
return 'Showcase a product with image and details';
}
public static function getKeywords(): array
{
return ['product', 'showcase', 'feature'];
}
// Required: Configure the editor modal form
public static function configureEditorAction(Action $action): Action
{
return $action
->modalDescription('Configure the product showcase')
->schema([
TextInput::make('title')
->required()
->maxLength(255),
Textarea::make('description')
->maxLength(500),
])->slideOver();
}
// Required: Render preview in admin editor
public static function toPreviewHtml(array $config): string
{
return view('cms.blocks.product-showcase', [
'title' => $config['title'] ?? 'Sample Product',
'description' => $config['description'] ?? 'Product description',
])->render();
}
// Required: Render on frontend
public static function toHtml(array $config, array $data): string
{
return view('cms.blocks.product-showcase', [
'title' => $config['title'] ?? '',
'description' => $config['description'] ?? '',
])->render();
}
}
Block Metadata (HasBlockMetadata)
| Method | Return Type | Default | Description |
|---|---|---|---|
getCategory() | string | 'content' | Category for grouping |
getIcon() | string | 'heroicon-o-cube' | Heroicon name |
getDescription() | string | '' | Brief description |
getKeywords() | array | [] | Search terms |
getSortPriority() | int | 50 | Sort order (lower = first) |
Categories
| Key | Label | Use For |
|---|---|---|
content | Content | Text, CTAs, pricing |
media | Media | Images, galleries |
social-proof | Social Proof | Testimonials, team |
dynamic | Dynamic | Posts, FAQ |
forms | Forms | Contact forms |
other | Other | Uncategorized |
Form Schema
Basic Components
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\FileUpload;
public static function configureEditorAction(Action $action): Action
{
return $action
->modalWidth('4xl')
->schema([
TextInput::make('title')
->required()
->maxLength(255),
Textarea::make('description')
->rows(3),
Select::make('style')
->options([
'default' => 'Default',
'highlight' => 'Highlighted',
])
->default('default'),
Toggle::make('show_button')
->default(true),
FileUpload::make('image')
->image()
->disk(cms_media_disk())
->directory('blocks/my-block'),
])->slideOver();
}
Repeater for Multiple Items
use Filament\Forms\Components\Repeater;
Repeater::make('items')
->schema([
TextInput::make('title')->required(),
Textarea::make('description'),
FileUpload::make('icon')->image(),
])
->defaultItems(3)
->minItems(1)
->maxItems(6)
->collapsible()
->reorderableWithButtons()
Template Development
Basic Template
{{-- resources/views/cms/blocks/product-showcase.blade.php --}}
<section class="py-12 px-4">
<div class="max-w-4xl mx-auto">
@if($title)
<h2 class="text-3xl font-bold mb-4 text-base-content">
{{ $title }}
</h2>
@endif
@if($description)
<p class="text-lg text-base-content/80">
{{ $description }}
</p>
@endif
</div>
</section>
DaisyUI Integration
<a href="{{ $url }}" class="btn btn-primary btn-lg">
{{ $button_text }}
</a>
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">{{ $title }}</h2>
<p>{{ $description }}</p>
</div>
</div>
Image Handling
@if($image)
<img
src="{{ Storage::disk(cms_media_disk())->url($image) }}"
alt="{{ $title }}"
class="w-full h-auto rounded-lg"
loading="lazy"
>
@endif
Advanced Patterns
Preview vs Frontend Rendering
public static function toPreviewHtml(array $config): string
{
// Provide sample data for preview
return static::renderBlock(array_merge($config, [
'title' => $config['title'] ?? 'Sample Title',
]), isPreview: true);
}
public static function toHtml(array $config, array $data): string
{
// Frontend uses actual data
return static::renderBlock($config, isPreview: false);
}
protected static function renderBlock(array $config, bool $isPreview = false): string
{
return view('cms.blocks.my-block', [
'isPreview' => $isPreview,
'title' => $config['title'] ?? '',
])->render();
}
Link Resolution
use TallCms\Cms\Services\BlockLinkResolver;
$buttonUrl = BlockLinkResolver::resolveButtonUrl($config, 'button');
Core Blocks Reference
TallCMS includes 16 built-in blocks:
| Block | ID | Category |
|---|---|---|
| Hero | hero | Content |
| Content | content_block | Content |
| Call to Action | call_to_action | Content |
| Features | features | Content |
| Pricing | pricing | Content |
| Divider | divider | Content |
| Media Gallery | image_gallery | Media |
| Document List | document_list | Media |
| Parallax | parallax | Media |
| Testimonials | testimonials | Social Proof |
| Team | team | Social Proof |
| Logos | logos | Social Proof |
| Stats | stats | Social Proof |
| Posts | posts | Dynamic |
| FAQ | faq | Dynamic |
| Timeline | timeline | Dynamic |
| Contact Form | contact_form | Forms |
Testing Blocks
Manual Testing Checklist
- Admin Preview: Insert block in editor, verify preview
- Form Validation: Test required fields, max lengths
- Frontend Display: Publish page, verify rendering
- Responsive Design: Test mobile, tablet, desktop
- Dark Mode: Verify styling in both modes
Common Issues
| Issue | Solution |
|---|---|
| Block not appearing | Verify class extends RichContentCustomBlock |
| Preview not updating | Check toPreviewHtml() returns valid HTML |
| Styles not applying | Run npm run build, check class names |
| Images not loading | Verify cms_media_disk() and file path |