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 Meta Laravel Package

tomatophp/filament-meta

TomatoPHP Filament Meta adds flexible meta data fields to Eloquent models and Filament resources, letting you manage SEO tags, custom attributes, and extra per-record settings directly from Filament forms and tables. A lightweight way to extend models without schema changes.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What this package actually is (and is not). tomatophp/filament-meta is a thin polymorphic key/value side table (model_type, model_id, key, value) with a HasMeta-style trait, a Meta Eloquent model, a published migration/config, and a Filament relation manager that edits those rows inline on any resource record. It is roughly a one-to-two-day implementation packaged for reuse. The capability ceiling is important for planning: it stores and surfaces metadata. It does not validate, type, filter, aggregate, localize natively, or enforce a schema. Any product requirement beyond "attach SEO fields to a record and edit them in the panel" is something you are building, not adopting.

Architecture fit. Strong fit for a single, bounded use case: per-record SEO authoring (title, description, keywords, Open Graph, Twitter card) inside an existing Filament resource. It is additive — a side table and a trait, no changes to existing models beyond use HasMeta and no changes to existing tables. That makes it reversible in a way most dependencies are not, which is the main reason it is worth a spike. It is a weak fit as a general-purpose metadata layer, as a query surface (filtering/sorting/reporting on meta values degrades immediately into EAV joins), and as a replacement for anything you already run for structured content.

Integration feasibility. Low-effort, high-verifiability. Install, publish migration/config, migrate, add the trait to one model, register the relation manager on the owning resource, and you have an editor-visible tab. The day-one decision gate is a ten-minute read of four files, and every one of them is a real risk question a TPM should own:

  • composer.json — does it constrain your Laravel, PHP, Filament, and Livewire majors? Version drift here is the most common reason a Filament package dies on contact.
  • The migration — is there a unique index on (model_type, model_id, key)? Is value a string or JSON? Is down() reversible? No unique index means duplicate keys are a when, not an if, and reads become nondeterministic.
  • src/Traits/HasMeta.php — what is the relation name, what helpers ship (meta, putMeta, hasMeta), does it honor morph maps?
  • The relation manager — does it route through Filament policies, and does it respect tenancy?

Technical risk. Concentrated and enumerable, which is good news for a plan:

  • Bus factor and supply chain. ~15 stars, 0 dependents, single maintainer, no public production evidence, and a reported last release in the future relative to typical review cycles — verify that date directly against Packagist before committing. There is no community buffer for a bad release. Mitigation is non-negotiable and cheap: pin the version, vendor or fork from day one, MIT license makes the exit a one-command operation.
  • Filament major upgrade coupling. Relation managers have broken across Filament v3→v4 before. This is the recurring, calendar-shaped risk, not a one-time one.
  • Correctness risks the package does not close for you. No unique index (duplicates), no key allowlist (free-text keys fragment into seo_titel/seo-title/seoTitle within a sprint), no validation (editors become the schema), string-only values (booleans round-trip as "1"), no automatic policy enforcement on the relation manager, no guaranteed cascade delete (orphan rows are silent rot), and no native per-locale model.
  • Performance regression. The single most likely production issue is N+1: $page->meta('seo_title') inside a table or Blade loop. This is entirely preventable with eager loading but must be an explicit acceptance criterion, not an assumption.

Key questions to resolve before approving a spike.

  1. Is SEO/metadata authoring in scope for this quarter, and is the owner one content model (Page or Post) or several?
  2. Do we already have a metadata store — a JSON meta column, Spatie medialibrary, a settings package? If yes, which keys does each own? Two homes for "metadata" is the most common way this quietly fails six months later.
  3. Does the migration carry the unique index, and if not, who writes the migration that adds it?
  4. Does the relation manager enforce our policies (test as a user who can view but not edit)? Does it respect Filament tenancy?
  5. Do we need localized SEO at launch? If yes, model it in the key (seo_title.en) — costs nothing now, migrates cleanly later.
  6. Who is the named internal owner of the fork?

Integration Approach

Stack fit. Assumes a Laravel + Filament panel already in place. The integration surface is two lines of code plus a migration — use HasMeta on the model, and the relation manager in getRelations() on the resource whose records own the meta (not a standalone resource). The intended production surface is exactly that: an editor opens a Page, sees a "Meta"/"SEO" tab, edits fields inline.

Migration path — a one-week spike, not a rollout. Sequence it as a bounded artifact with a go/no-go at the end:

  1. Day 1 — diligence. Read the four files above. Verify the Packagist release date. Confirm the package targets your Filament major. If the unique index is missing, write your own migration adding it before any row is written.
  2. Day 1 — install and pin. composer require, publish migration and config (verify the publish tag names against the README), migrate. Pin to an exact tag or 1.0.*; do not float. Vendor the source and confirm the fork builds.
  3. Day 2 — wrap it. Do not let package types leak into resources, models, or Blade. One wrapper trait is the entire exit strategy: it centralizes meta($key, $default), putMeta() via updateOrCreate (never create), key normalization, and cache/relation flushing. Every call site — Blade, controllers, sitemap generators, seeders, imports, backfills — reads and writes through this one file. When you later move to a JSON column or typed fields, you change the wrapper and a backfill, not every call site.
  4. Day 2 — constrain the keys. Define an enum or constant allowlist and enforce it in both the write helper and the relation manager's form (a Select on key, not a free TextInput). Normalize and validate at the write helper, not only in the form — seeders and imports bypass the form.
  5. Day 3 — the fallback chain. Raw meta is almost never complete; editors fill ~60% and expect it to look finished. Centralize seoTitle() / seoDescription() with fallbacks so every template behaves identically, and reuse them in the sitemap. The same helpers double as a cheap coverage metric.
  6. Day 3–4 — permission and performance gates. Exercise create/edit/delete end to end, then once as a user who can view the Page but should not edit it. Measure query count on the resource list view with and without with('meta'). Both are pass/fail, not best-effort.
  7. Day 4–5 — coexistence doc and rollback drill. Write down which store owns which keys if you already have meta plumbing. Run migrate:rollback in the sandbox. Scope the cost of leaving (migration to a JSON column or dedicated columns) now, while there are forty test rows instead of forty thousand.

Compatibility. Define Relation::enforceMorphMap() in AppServiceProvider — a meta table full of FQCNs is a refactor trap you inherit permanently. Add a deleted observer or scheduled cleanup if the package does not cascade. If value is not cast to JSON, restrict meta to genuinely textual data and keep booleans/numbers in real columns, or cast deliberately in the wrapper with a marker.

Sequencing relative to product. This sits cleanly ahead of a broader content/CMS initiative: it converts a "make vs. buy vs. defer" debate over SEO into a one-week spike with a real artifact instead of an estimate. It should not be sequenced ahead of a decision about whether SEO is in scope at all, and it should not be sequenced in parallel with a Filament major upgrade.

Deliberate non-goals for the spike. Do not build a keyed repeater or custom field-group UI on top of the relation manager in week one; the row-per-key UX is acceptable for ad-hoc keys, and a prettier editor is a follow-up. Do not plan to filter, sort, export, or report on meta values — if a key needs that at volume, promote it to a real column and say so in the conventions doc before the first such request arrives.

Operational Impact

Maintenance. Highest-ongoing-cost item is not the table, it is the upstream relationship. Plan for the fork to be the support contract: assign an internal owner by name, keep the fork buildable, and do not expect upstream for urgent fixes. Budget a small recurring calendar item for Filament upgrade windows — branch, sandbox the relation manager, check for deprecated APIs. If upstream is abandoned, the vendored copy is the fix and the license permits it.

Support surface. Editor-facing support volume is driven by two things, both addressable cheaply. First, key drift — solved by the allowlist and a short conventions doc covering keys and their formats (title ~60 chars, description ~155, comma-separated keywords, strip tags). Second, "why does this page look wrong" — solved by making completeness visible: a boolean badge or icon column on the resource table showing whether title and description are populated. Eager-load meta on the listing query or the badge itself becomes the N+1.

Scaling. Adequate to very good for the use case it targets: a handful of key-value rows per content record, read through an eager-loaded relation. Degradation is predictable and comes from three places — N+1 reads in loops (preventable), filtering/sorting/aggregating on meta values (a design boundary, documented up front), and unbounded key growth from free-text entry (prevented by the allowlist). Note that a polymorphic meta row carries no tenant column; tenancy is inherited from the owner and only if the owner lookup is scoped, so multi-tenant panels need that verified explicitly rather than assumed.

Failure modes, ranked by likelihood and blast radius.

  1. N+1 queries on list views and templates — most likely, most preventable, add a query-count assertion to acceptance criteria.
  2. Duplicate keys from a missing unique index or a create-based write path — presents as "works on my machine, wrong title in production," miserable to trace because the first row wins nondeterministically. Fix is a unique index plus updateOrCreate everywhere.
  3. Authorization gaps — a relation manager registered on a resource does not guarantee it routes through policies; override canViewForRecord() / canEdit() gated on update of the owner.
  4. Orphaned meta rows after model deletion — silent data rot; observer or scheduled cleanup.
  5. Filament major upgrade breakage — schedule it, don't discover it.
  6. Two stores for metadata diverging — the failure mode with the longest fuse; decide ownership in writing before the spike ships.

Ramp-up. Low for a Laravel/Filament-fluent engineer: one to two days to a working spike, with the four-file read being the ten-minute version. The wrapper trait is the only piece requiring real design judgment and is worth a short review. Debugging has a clean, ordered checklist worth putting in the runbook: SHOW CREATE TABLE on the meta table (indexes, column types) → the relation manager's getQuery() (scoping) → DB::listen() during a list-view render (N+1) → row count per model_type to spot orphans and duplicates → policy check as a non-owner user.

Exit strategy. Cheap and known: the wrapper trait confines the blast radius to one file, MIT licensing makes fork-and-patch trivial, and the data migration to a JSON column or dedicated columns is a scoped exercise — provided you scope it during the spike rather than after the table has grown. The real answer to "what does this cost us if it dies" is roughly "a day of migration work plus a fork we already own," which is a reasonable price for removing an estimate from the SEO decision.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle