blackpig-creatif / sceau
SEO and social media optimization for FilamentPHP v4
Installs: 4
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/blackpig-creatif/sceau
Requires
- php: ^8.2|^8.3|^8.4
- blackpig-creatif/chambre-noir: ^1.0
- filament/filament: ^4.0
- illuminate/contracts: ^11.0|^12.0
- lara-zeus/spatie-translatable: ^1.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-09 18:20:59 UTC
README
SEO metadata management plugin for FilamentPHP v4
Sceau provides comprehensive SEO metadata management for Laravel applications using Filament v4. 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
- AI Optimization - Content freshness signals and FAQ schema for modern search engines
- Blade Component - Simple
@seo($model)directive for frontend rendering
Requirements
- PHP 8.2+
- Laravel 11.0+ or 12.0+
- Filament 4.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); // Push runtime schemas 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); // Product list with pricing Schema::push(ProductListSchema::makeDetailed($products)); // Breadcrumbs 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
Block-Based Schema Generation
For pages built with Atelier blocks, use the PageSchemaBuilder:
use BlackpigCreatif\Sceau\Services\PageSchemaBuilder; public function show(Page $page) { // Automatically generates schemas from all page blocks PageSchemaBuilder::build($page); return view('pages.show', ['page' => $page]); }
This will:
- Generate an Article schema from text/image blocks
- Generate standalone schemas from FAQ, Video, and other special blocks
- Respect block configuration (published status, etc.)
Atelier Block Integration
Add schema contribution to your Atelier blocks using the InteractsWithSchema trait:
Text Block (contributes to Article):
use BlackpigCreatif\Atelier\Blocks\BaseBlock; use BlackpigCreatif\Sceau\Concerns\InteractsWithSchema; class TextBlock extends BaseBlock { use InteractsWithSchema; public function contributesToComposite(): bool { return true; } public function getCompositeContribution(): array { return [ 'type' => 'text', 'content' => $this->data['content'] ?? '', ]; } }
FAQ Block (generates standalone schema):
use BlackpigCreatif\Atelier\Blocks\BaseBlock; use BlackpigCreatif\Sceau\Concerns\InteractsWithSchema; class FaqBlock extends BaseBlock { use InteractsWithSchema; public function hasStandaloneSchema(): bool { return !empty($this->data['pairs']); } public function toStandaloneSchema(): ?array { return [ '@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => collect($this->data['pairs'])->map(fn($pair) => [ '@type' => 'Question', 'name' => $pair['question'], 'acceptedAnswer' => [ '@type' => 'Answer', 'text' => $pair['answer'], ], ])->toArray(), ]; } }
See /examples directory for more block implementations.
Multiple Schemas Per Page
Schema.org supports (and Google expects) multiple independent schemas on a single page:
public function show(Page $page) { // Article content from blocks PageSchemaBuilder::build($page); // Product carousel at bottom Schema::push(ProductListSchema::make($featuredProducts)); // Category FAQs Schema::push([ '@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => [...], ]); // Breadcrumbs Schema::push(BreadcrumbListSchema::make($breadcrumbs)); }
Final output will be an array of schemas, each validated independently by Google.
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; // Custom logic for multi-author support 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); } protected function getPublisher(SeoData $seoData): array|null { return [ '@type' => 'Organization', 'name' => config('app.name'), 'logo' => [ '@type' => 'ImageObject', 'url' => asset('images/logo.png'), ], 'url' => config('app.url'), ]; } }
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); } }
Register it:
use App\SEO\Schemas\CourseSchema; $generator->registerGenerator( SchemaType::Course, // Add this to SchemaType enum first new CourseSchema );
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); }
Now whenever JsonLdGenerator instantiates new ArticleSchema, Laravel's container will resolve your custom implementation instead.
Accessing SEO Data
The HasSeoData trait provides convenient accessor methods:
$page = Page::with('seoData')->first(); // Relationship $seoData = $page->seoData; // Helper methods $title = $page->getSeoTitle(); // Falls back to $page->title or $page->name $description = $page->getSeoDescription(); $hasSchema = $page->seoData?->hasSchemaMarkup(); $hasFaq = $page->seoData?->hasFaqPairs(); // Specific attribute with fallback $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.
License
MIT License. See LICENSE for details.