SEO and social media optimization for FilamentPHP v4

Installs: 14

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/blackpig-creatif/sceau

v1.1.2 2026-02-25 15:39 UTC

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:

  1. Model-Level Schemas (SeoData) - For entity-specific metadata (Organization, LocalBusiness, Product models)
  2. 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 blocks
  • ProductListSchema::make($products, $transformer) - Generate ItemList
  • ProductListSchema::makeDetailed($products) - ItemList with pricing/images
  • BreadcrumbListSchema::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:

  1. Article schema — assembled from all blocks implementing HasCompositeSchema (text content, image URLs)
  2. Legacy standalone schemas — blocks implementing HasStandaloneSchema that build the full schema array themselves
  3. Driver-based typed schemas — blocks implementing HasSchemaContribution that declare a SchemaType and provide data; SceauBlockSchemaDriver constructs 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:

  1. Add a case to the match expression in resolveSchema()
  2. Add a protected buildXxxSchema(array $data): ?array method
  3. Add a fromXxx(array $data): array static 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:

  1. Check for explicitly uploaded images in SEO data
  2. Fall back to the first published Atelier hero block's background image
  3. Apply the appropriate Chambre Noir conversion (og, twitter, etc.)

Override this behavior by extending SeoData and customizing the image resolution methods.

Documentation

License

MIT License. See LICENSE for details.