blackpig-creatif / sceau
SEO and social media optimization for FilamentPHP v4
Requires
- php: ^8.2|^8.3|^8.4
- blackpig-creatif/chambre-noir: ^1.0
- blackpig-creatif/grimoire: ^1.0
- filament/filament: ^4.0|^5.0
- illuminate/contracts: ^11.0|^12.0
- lara-zeus/spatie-translatable: ^1.0|^2.0
- spatie/laravel-package-tools: ^1.16
- spatie/laravel-translatable: ^6.0
Requires (Dev)
- orchestra/testbench: ^9.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-02-25 15:42:50 UTC
README
SEO metadata management plugin for FilamentPHP v5
Sceau provides comprehensive SEO metadata management for Laravel applications using Filament v5. It offers a polymorphic approach to SEO data, allowing any Eloquent model to have associated meta tags, Open Graph data, Twitter Cards, and JSON-LD structured data.
Features
- Polymorphic SEO Data - Attach SEO metadata to any Eloquent model
- Filament Integration - Manage SEO through a clean RelationManager interface
- Meta Tags - Title, description, canonical URLs, robots directives
- Social Media - Open Graph and Twitter Card support
- Structured Data - Extensible JSON-LD schema generators for Schema.org markup
- Settings Management - Database-backed global SEO settings via Filament panel
- Image Processing - Integrated with Chambre Noir for optimized OG/Twitter images
- Atelier Integration - Automatic schema generation from content blocks with no controller boilerplate
- Blade Component - Simple
@seo($model)directive for frontend rendering
Requirements
- PHP 8.2+
- Laravel 11.0+ or 12.0+
- Filament 5.0+
Installation
composer require blackpig-creatif/sceau
Run the migration:
php artisan migrate
Optionally publish the config file:
php artisan vendor:publish --tag=sceau-config
Basic Usage
1. Add the Trait to Your Model
use BlackpigCreatif\Sceau\Concerns\HasSeoData; class Page extends Model { use HasSeoData; }
2. Add the RelationManager to Your Filament Resource
use BlackpigCreatif\Sceau\Filament\RelationManagers\SeoDataRelationManager; class PageResource extends Resource { public static function getRelations(): array { return [ SeoDataRelationManager::class, ]; } }
3. Render SEO Tags in Your Layout
<head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> @seo($page) </head>
Or use the component directly:
<x-sceau::head :model="$page" />
Configuration
The config file (config/sceau.php) allows you to customize:
- Upload disk and directory for OG/Twitter images
- Character limits for titles and descriptions
- Default values for robots directives, OG types, Twitter card types
- Available schema types (filter which types appear in the select)
Global SEO Settings
Sceau includes a settings page accessible from your Filament panel at Settings > SEO Settings. Configure:
- Site name and URL
- Contact information (phone, email)
- Business address
- Price range and opening hours (for LocalBusiness schema)
To add the settings page to your panel, register the plugin:
use BlackpigCreatif\Sceau\SceauPlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ SceauPlugin::make(), ]); }
Schema Architecture
Sceau uses a dual-layer approach to schema generation:
- Model-Level Schemas (SeoData) - For entity-specific metadata (Organization, LocalBusiness, Product models)
- Runtime Schemas (SchemaStack) - For page-specific content assembled at request time
This separation aligns with how modern web applications work: pages are built dynamically from blocks, products are fetched by controllers, and breadcrumbs depend on the current route.
Model-Level Schemas (SeoData)
These schemas describe entities that exist as models in your database:
- Organization - Company/organization data from settings
- LocalBusiness - Business with address/hours from settings
- Product - For dedicated Product models with SEO data
- Person - For author/profile models
Configure these through the Filament RelationManager on your models.
Runtime Schemas (SchemaStack)
These schemas describe content assembled at runtime:
- Article/BlogPosting - Built from Atelier blocks
- ItemList - Product listings, search results
- BreadcrumbList - Navigation breadcrumbs
- FAQPage - FAQ blocks on pages
- VideoObject - Video blocks
Runtime Schema Generation
The Schema Facade
Push schemas onto the stack during request processing:
use BlackpigCreatif\Sceau\Facades\Schema; public function show(Page $page) { $products = Product::where('category_id', $page->category_id)->paginate(24); Schema::push([ '@context' => 'https://schema.org', '@type' => 'ItemList', 'numberOfItems' => $products->total(), 'itemListElement' => $products->map(fn ($product, $index) => [ '@type' => 'ListItem', 'position' => $index + 1, 'item' => [ '@type' => 'Product', 'name' => $product->name, 'url' => route('products.show', $product), ], ])->values()->toArray(), ]); return view('pages.show', compact('page', 'products')); }
Runtime Schema Helpers
Use helper classes for common patterns:
use BlackpigCreatif\Sceau\Facades\Schema; use BlackpigCreatif\Sceau\Schemas\Runtime\ProductListSchema; use BlackpigCreatif\Sceau\Schemas\Runtime\BreadcrumbListSchema; public function show(Page $page) { $products = Product::paginate(24); Schema::push(ProductListSchema::makeDetailed($products)); Schema::push(BreadcrumbListSchema::make([ ['name' => 'Home', 'url' => route('home')], ['name' => 'Products', 'url' => route('products.index')], ['name' => $page->category->name, 'url' => route('category', $page->category)], ])); return view('pages.show', compact('page', 'products')); }
Available helpers:
ArticleSchema::fromBlocks($blocks, $seoData)- Generate Article from Atelier blocksProductListSchema::make($products, $transformer)- Generate ItemListProductListSchema::makeDetailed($products)- ItemList with pricing/imagesBreadcrumbListSchema::make($breadcrumbs)- Generate BreadcrumbList
Multiple Schemas Per Page
Schema.org supports (and Google expects) multiple independent schemas on a single page:
public function show(Page $page) { // Article content + typed block schemas from Atelier (auto-wired via Head component) // Additional runtime schemas pushed from the controller Schema::push(ProductListSchema::make($featuredProducts)); Schema::push(BreadcrumbListSchema::make($breadcrumbs)); }
Final output will be an array of schemas, each validated independently by Google.
Atelier Block Integration
When Atelier is installed alongside Sceau, the <x-sceau::head> component automatically generates Schema.org output from your page's blocks. No controller code is required.
{{-- This is all you need --}} <x-sceau::head :model="$page" />
For the complete integration reference, see docs/atelier-integration.md.
How it works
The Head component calls PageSchemaBuilder::build($page) automatically for any model that has publishedBlocks. The builder runs three passes over the blocks:
- Article schema — assembled from all blocks implementing
HasCompositeSchema(text content, image URLs) - Legacy standalone schemas — blocks implementing
HasStandaloneSchemathat build the full schema array themselves - Driver-based typed schemas — blocks implementing
HasSchemaContributionthat declare aSchemaTypeand provide data;SceauBlockSchemaDriverconstructs the schema array using Sceau's generators
Wiring the driver
In your application's config/atelier.php:
'schema_driver' => \BlackpigCreatif\Sceau\Schema\Drivers\SceauBlockSchemaDriver::class,
This registers SceauBlockSchemaDriver as the singleton implementation of BlockSchemaDriverInterface. The driver matches on SchemaType value and delegates construction to Sceau's schema generators.
Built-in block schemas
| Block | Schema output |
|---|---|
TextBlock |
Article body text |
TextWithImageBlock |
Article body text + image |
TextWithTwoImagesBlock |
Article body text + two images |
GalleryBlock |
Article image URLs |
CarouselBlock |
Article image URLs |
VideoBlock |
VideoObject |
FaqsBlock |
FAQPage |
Adding a new SchemaType
To support a new type in SceauBlockSchemaDriver:
- Add a case to the
matchexpression inresolveSchema() - Add a
protected buildXxxSchema(array $data): ?arraymethod - Add a
fromXxx(array $data): arraystatic method to the corresponding schema generator
Extending SeoData Schemas
Overriding Methods in a Subclass
Create a local schema class that extends one of the base generators:
namespace App\SEO\Schemas; use BlackpigCreatif\Sceau\SchemaGenerators\ArticleSchema; use BlackpigCreatif\Sceau\Models\SeoData; class CustomArticleSchema extends ArticleSchema { protected function getAuthor(SeoData $seoData): array|null { $page = $seoData->seoable; if ($page->authors && $page->authors->count() > 0) { return $page->authors->map(fn ($author) => [ '@type' => 'Person', 'name' => $author->name, 'url' => route('authors.show', $author), ])->toArray(); } return parent::getAuthor($seoData); } }
Register your custom schema in AppServiceProvider:
use BlackpigCreatif\Sceau\Services\JsonLdGenerator; use BlackpigCreatif\Sceau\Enums\SchemaType; use App\SEO\Schemas\CustomArticleSchema; public function boot(): void { $generator = app(JsonLdGenerator::class); $generator->registerGenerator( SchemaType::Article, new CustomArticleSchema ); }
Creating Custom Schema Types
For schema types not included in the package:
namespace App\SEO\Schemas; use BlackpigCreatif\Sceau\SchemaGenerators\BaseSchema; use BlackpigCreatif\Sceau\Models\SeoData; class CourseSchema extends BaseSchema { public function getType(): string { return 'Course'; } public function generate(SeoData $seoData): array { $schema = $this->baseSchema(); $course = $seoData->seoable; $schema['name'] = $this->getName($seoData); $schema['description'] = $this->getDescription($seoData); $schema['provider'] = [ '@type' => 'Organization', 'name' => config('app.name'), ]; if ($course->price) { $schema['offers'] = [ '@type' => 'Offer', 'price' => $course->price, 'priceCurrency' => 'USD', ]; } return $this->removeNullValues($schema); } }
Container Binding for Global Override
To replace a schema generator globally without touching the package:
use BlackpigCreatif\Sceau\SchemaGenerators\ArticleSchema; use App\SEO\Schemas\CustomArticleSchema; public function register(): void { $this->app->bind(ArticleSchema::class, CustomArticleSchema::class); }
Accessing SEO Data
The HasSeoData trait provides convenient accessor methods:
$page = Page::with('seoData')->first(); $title = $page->getSeoTitle(); // Falls back to $page->title or $page->name $description = $page->getSeoDescription(); $hasSchema = $page->seoData?->hasSchemaMarkup(); $hasFaq = $page->seoData?->hasFaqPairs(); $ogTitle = $page->getSeoAttribute('open_graph.title', 'Default Title');
Advanced: Image Resolution
Sceau integrates with Chambre Noir for image processing and supports Atelier blocks for hero image resolution.
When rendering OG/Twitter images, the package will:
- Check for explicitly uploaded images in SEO data
- Fall back to the first published Atelier hero block's background image
- Apply the appropriate Chambre Noir conversion (
og,twitter, etc.)
Override this behavior by extending SeoData and customizing the image resolution methods.
Documentation
- Atelier Integration -- automatic block schema generation reference
License
MIT License. See LICENSE for details.