Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Seo Laravel Package

tomatophp/filament-seo

A Filament plugin for Laravel admin panels that lets you manage SEO titles, meta descriptions, Open Graph tags, and related metadata for your models and pages directly from the dashboard.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is a Filament panel plugin, not a framework-level concern. That is the single most important architectural fact: it inserts an editorial surface into the admin panel and a thin Blade partial into the public <head>, and touches nothing else. The persistent model is a polymorphic seos table reached through a HasSeo trait exposing a morphOne — idiomatic Eloquent, familiar to any Laravel engineer on the team, and decoupled from the package's view layer. That decoupling is what makes the adoption decision reversible: if the Blade component drifts or breaks, the data model still works and we swap the partial.

Fit is high for a server-rendered Laravel + Filament monolith. Fit is low for headless or SPA front ends (Inertia, Next, Nuxt) — the editing surface transfers, but the rendering half, which is where the SEO value is realised, does not. Confirm the front-end render path before this goes on a roadmap.

Integration feasibility. The happy path is genuinely small: composer require, publish config and migrations, php artisan migrate, register the plugin in the PanelProvider, add the trait to one model, compose a collapsed Section::make('SEO')->relationship('seo') into an existing resource form, include the view in the layout. A single engineer can complete this end to end on one model inside two days. There is no service tier, no external API dependency for the baseline capability, and no data migration of existing content required — SEO records simply start existing per model.

The two things that make feasibility non-trivial are both pre-existing conditions in our codebase, not defects in the package:

  • A competing meta emitter. If artesaos/seotools, a hand-rolled partials.meta, or a hard-coded title in app.blade.php is already in place, we will ship two <title> tags or contradictory OG tags and no test will fail. The package can legitimately be the editing surface while the existing renderer remains the output — that split is often the correct migration — but it must be an explicit architectural decision, documented, not an accident of sequencing.
  • Tenant scoping. The polymorphic columns carry no tenant discriminator by default. In a single-tenant app this is a non-issue. In a multi-tenant database it is a data-correctness question that must be answered before a pilot, not after.

Technical risk. Ranked by expected cost, not by likelihood:

Risk Severity Mitigation
Silent output regression — duplicate robots, missing canonical, 404 OG image. Nothing throws; traffic erodes over weeks. High Snapshot test on the head block, plus a crawl diff on every release touching the head. This is the one thing built on day one, not later.
Bus factor of one. 15 stars, 0 dependents, single maintainer, MIT. High Exact version pin, named internal owner, confirmed ability to fork the ~small surface (trait, model, form schema, partial).
Filament major-version drift. Panel plugins break on Filament majors more reliably than on anything else. Medium-High Read composer.json constraints in vendor/ on day one; that file is a better roadmap signal than the README.
Google integration placing an outbound API call in the request path, or storing OAuth secrets in an admin-editable settings page. Medium Any Google call runs as scheduled work with backoff; secrets in .env/config(), only the property ID admin-editable; staging runs against a non-production property to observe the revoked-token failure mode.
Multi-tenant bleed through an unscoped polymorphic relation. Medium (binary if multi-tenant) Verified tenant isolation before pilot, or a tenant_id column plus global scope.
Unclean exit — no documented export path; authored metadata is stranded. Low-Medium Write a one-off seo:export to JSON before committing. Cheap insurance that makes "we can walk away" literally true.

Key questions to resolve before approval.

  1. Which Filament major are we on, and does the package's constraint cover it — with room for our next upgrade?
  2. What is our current meta-tag emitter, and is it being replaced, retired, or retained as the output layer?
  3. Is the public site Blade-rendered? If not, is the editorial surface alone worth the adoption?
  4. Single-tenant or multi-tenant database for the pilot target?
  5. Who is the named internal owner, and what is the quarterly maintenance allocation?
  6. Is the Google Search Console / Analytics integration in scope for v1, or explicitly deferred? (Recommend deferred — it carries most of the operational risk and little of the editorial value.)
  7. Does sitemap generation handle our route volume and dynamic content, or do we keep our existing sitemap and use this only for metadata?

Integration Approach

Stack fit. Laravel + Filament panel, Composer-managed, publishable config and migrations, Blade-rendered public output. Nothing exotic. The package assumes it is the only SEO plugin in the panel and that the front end is server-rendered — both assumptions must be validated against our codebase rather than assumed from the README.

Migration path — dual-run, rendering last. The product value is editorial workflow; workflow has no blast radius. Rendering does. Sequence accordingly:

Phase Scope Gate to proceed Effort
0 — Spike Install pinned version, register plugin, trait on one Post/Page, SEO section in the resource form, legacy head renderer untouched. Walk one record end to end, including a non-ASCII record. Head output unchanged; editor can save and reload metadata; no N+1 in the admin list. 1–2 eng-days
1 — Admin-only in production One model. Editors author metadata that is stored but not rendered. Percentage of published records with filled title + description trending up. < 1 day
2 — Shadow render Package partial renders behind a flag for one route group; diff emitted <head> against legacy across a page sample. Zero unexpected diffs; snapshot test committed and green. 1–2 eng-days
3 — Flip per route group Marketing pages, then blog, then product. Legacy partial retained for one release cycle as a documented rollback. Crawl diff clean per group. Incremental
4 — Extend model by model Blog → pages → products. One entity per change so a regression is attributable. No new N+1; no editor confusion reported. Incremental
5 — Google integration Jobs, backoff, staging against a non-production property. Revoked-token failure mode observed and alerts wired. Deferred; separate decision

Do not compress phases 2 and 3. Phase 1 can ship on a Tuesday afternoon.

Compatibility work that is ours, not theirs. Publish the views and edit rather than overriding in the layout — it keeps the diff against upstream legible on the next bump. Compose the package's own form schema class into our Section rather than hand-rolling the field list, so fields the maintainer adds appear after composer update. Pin the exact version; do not inherit a caret range. Add the seo relation to base queries (controller, index, sitemap loop) rather than sprinkling ->load('seo'). Derive canonical URLs in code from route + slug and expose them read-only; editable canonical strings produce self-referencing canonicals on paginated pages.

Scope discipline. Adopting this funds editorial hygiene — per-record metadata, social cards, verification tags — not search strategy. Hreflang, JSON-LD authoring, redirect management, and programmatic page generation stay explicitly out of scope and out of the "we shipped SEO" milestone. Making that line visible in the roadmap is the point: it lets us defer the strategic bucket deliberately rather than by omission.

Operational Impact

Maintenance. This is a single-maintainer MIT dependency at 15 stars with 0 dependents. It will not be maintained on our cadence. Assume an internal owner with a standing allocation of roughly two engineer-days per quarter for version bumps and head-output verification, plus ad-hoc time when a Filament major lands. The code surface is small enough that forking the view layer is an afternoon's work — that is the real contingency, and it should be stated in the adoption record so it isn't rediscovered under pressure. Do not let this end up unowned in the dependency graph; an unowned SEO plugin is how you get a canonical tag silently dropped for a quarter.

Support. There is no vendor. Upstream responsiveness at this star count is unknown and should not be on the critical path. Editor-facing support is ours: the failure they will hit is the blank field, since editors skip inputs they don't understand. Seed SEO title and description from the content fields (->default(fn (?Post $record) => $record?->title)) so editors edit rather than author, truncate with Str::limit(..., 155) at render time, and guard each tag with filled() rather than isset() — an empty record stores '', not null, and truthiness passes for whitespace. <meta name="description" content=""> is worse than no tag.

Scaling. Two cost centres, both predictable:

  • N+1 on the morphOne. Every listing, index, or sitemap loop touching $model->seo fires a query per row. Eager-load on the base query and cache the resolved tag array per model with a short TTL, busted by a model observer on save — head output changes rarely and is read on every request. This is the most common performance regression from any polymorphic SEO package and it is entirely within our control.
  • Sitemap and robots.txt generation. Fine at modest route volumes; at scale, generate as a scheduled job into cache or static output rather than on request, and confirm whether the package's sitemap handles our dynamic route set or whether we retain the existing generator and use this package for metadata only. robots.txt is effectively static and low-risk.

Multi-byte content is a five-minute check during the spike: a "155-character" description in CJK or emoji can exceed a downstream byte limit. Test with one non-ASCII record and close the class of bug before production.

Failure modes and detection. The defining property of this integration is that wrong output produces no error. Duplicate robots tags, a missing canonical, an OG image that 404s, two <title> tags from a surviving legacy emitter — all render a 200 and log nothing. Detection has to be manufactured:

  • Head-block snapshot test in CI, asserted against a stored fixture. This is the gate on every release that touches the head, not a nice-to-have.
  • Crawl diff (Screaming Frog or a head-extraction script over the sitemap) on each release touching the render path.
  • Alerting on the Google integration's job failures, with the revoked-refresh-token path exercised in staging against a non-production property. A synchronous Google call in a Filament page render converts a Google outage into an admin outage — outbound calls belong in jobs with backoff.
  • Tenant isolation test before the pilot if we are multi-tenant. Cross-tenant metadata is the one failure mode with a compliance dimension.

Ramp-up. Engineering: two days to the end of Phase 0, two more to shadow render. Editors: roughly an hour of enablement, focused on "these fields default from your content — edit them, don't skip them." First measurable value is available in week one if Phase 1 ships to production: the share of published records with a filled, unique title and description. That metric, plus a clean crawl diff and zero pages missing a canonical, is a sufficient success definition for the milestone. The exit plan — seo:export to JSON, legacy partial retained for one release — costs an afternoon and is what makes the adoption decision genuinely reversible.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource