ralphjsmit/laravel-filament-seo
A Filament PHP fieldgroup that edits the title, author, and description of a laravel-seo model. It automatically loads and saves data through the seo() relationship, so you can drop SEO::make() into any form with no extra configuration.
This package is a thin Filament adapter: it renders a single field group for SEO title, author, description, and (since 1.4.0) a robots control, and it transparently reads/writes through the model's polymorphic seo() relationship. Everything else — schema, migration, config, and front-end rendering — belongs to ralphjsmit/laravel-seo. Treat it as an admin-time-saver, not an SEO platform.
Minimal sequence:
composer require ralphjsmit/laravel-filament-seo — this pulls ralphjsmit/laravel-seo if absent.seo.php config and run its migration. If you skip this, the component will render but saving will hit a missing table or mis-configured model.use HasSEO; to every model that should expose SEO fields.SEO::make() into any Filament form schema.First use case: a PostResource form where editors manage per-record metadata alongside the post title. It works identically for create and edit with no extra wiring — that "no configuration" claim is the whole value proposition, so validate it in ten minutes on one resource before planning wider rollout.
Where to look first: the parent laravel-seo documentation and its seo.php config (that's where URLs, model binding, and defaults live), then the SEO class source in this package to confirm which Filament component it extends on your version. Do not trust the README's code samples verbatim — see Gotchas.
Resource form (primary pattern). Insert SEO::make() after your content fields, ideally wrapped in a Section or Card so editors understand it's a metadata block rather than body content. It is a form component, so normal layout and visibility modifiers compose with it:
SEO::make()
->visible(fn ($get) => $get('type') === 'article'),
This is the cheapest way to scope SEO to only the record types that actually need it.
Standalone Filament Forms / Livewire. Two distinct shapes, and they differ in one critical step:
mount() from the existing record, use getFormModel() to return the model, then $model->update($this->form->getState()) on submit. The relationship is already loaded, so persistence just works.Post::create($this->form->getState()) does not save the SEO relationship. You must follow it with $this->form->model($post)->saveRelationships(). Omitting this line is the single most common integration failure with this package, and it fails silently — the post saves, the SEO row never appears.Reuse across models. Because the relationship is polymorphic, one SEO::make() call serves every resource. Define it once in a small helper or trait method if you want consistent placement and labelling across many resources, rather than copy-pasting the component and drifting.
Pair with front-end rendering. The value only lands when the parent package actually emits the metadata in your Blade layout. Wire the render call into your base template in the same PR as the admin field, otherwise editors will populate fields nobody consumes.
Acceptance test matrix. Given the changelog history — create-on-save semantics (0.6.0), polymorphic updateOrCreate (0.6.2), dynamic SEO model (0.7.0) — write explicit tests for: create a record with SEO data, edit and change it, re-save without touching SEO (does it clobber or preserve?), and load an existing record (does the form hydrate the SEO values?). Also test the "no SEO row yet" edit path, since existing records in your database won't have one.
The README is written for Filament 2.x. Examples import Filament\Resources\Form and use getFormSchema(), but the package has shipped V3, V4, and V5 support. On any modern Filament install you'll translate the snippet to Filament\Schemas\Schema / Filament\Forms\Components\* and public static function form(Schema $schema): Schema. Budget an hour for source-reading the first time; don't hand this task to a junior expecting copy-paste to work.
Pin the version to your Filament major, deliberately. The release line and the Filament major are coupled: 1.2.x for V3, 2.0.x for V4, 2.1.x+ for V5, with 2.x lines tracking Laravel 11/12/13. Nearly every changelog entry is upstream-compatibility work, which means your Filament upgrade plan and this package's release cadence are the same plan. Pin explicitly in composer.json and re-check availability before starting a Filament major bump.
Persistence is the risk surface, not the UI. The rendering is boring and reliable; the save path has historically been where bugs lived. If SEO data isn't saving, check in this order: trait present on the model, parent migration run, relationship correctly named, and — for standalone create forms — the saveRelationships() call present.
Config lives upstream. Everything meaningful (SEO model class, URL resolution, defaults) is in laravel-seo's seo.php. If you need a custom SEO model, note that dynamic model support arrived in 0.7.0 and must be configured there, not in this package. Don't patch this package for config concerns.
Labels are translatable (1.2.1), and French, Arabic, Spanish, Turkish, and Italian translations ship. If you're localising the admin, publish/override the translation keys rather than hardcoding labels. The description field has had a character counter since 0.3.0 — useful for editor guidance, but it's a counter, not a validation rule; enforce limits yourself if you care.
Scope boundary — say it out loud early. This gives you per-record metadata editing. It does not give you sitemaps, redirects, JSON-LD, Open Graph images, SEO scoring, audits, or analytics. Stakeholders who hear "SEO package" will assume the suite; correct that in the kickoff, not at demo time.
Adoption and bus factor. 153 stars, 0 dependents, single maintainer. That's fine for a forkable convenience layer in an existing Filament admin, and inappropriate as a load-bearing dependency you can't replace. Keep the blast radius small: it's one component in one form schema, so if it stalls on a Filament upgrade you can inline the equivalent fields against the parent package in a day. Verify the robots control's presence and default on your pinned version (1.4.0+) before telling editors it exists.
How can I help you explore Laravel packages today?