wotz/filament-belongs-to-many
A Filament form field for managing belongsToMany relationships with a searchable, sortable multi-select UI. Attach and detach related records inline, with support for pivot data and custom labels. A lightweight alternative to the default relationship component, built for Laravel and Filament v3+.
Architecture fit. The package targets a narrow, well-defined seam in the TALL stack: a Filament form field (and presumably a table column) for many-to-many relationships expressed as a Laravel belongsToMany with a pivot table. Architecturally this is a low-ceremony Composer package — a service provider for Filament panel registration plus one or more Field/Column subclasses. It introduces no new runtime, no queue workers, no migrations, and no persistence layer of its own, so the blast radius on an existing application is small and contained to the form/table rendering path it decorates.
The critical architectural question is duplication of core capability. Filament ships a first-party BelongsToMany form component and matching table column that already cover multi-select, checkbox list, and tag-style input against a pivot table. For this package to earn a dependency slot it must beat core on a specific axis — inline pivot-attribute editing, attach/detach modal UX, pivot ordering, or relation-manager parity. Nothing in the metadata establishes that differentiation. Absent a demonstrable gap, the correct architecture decision is to use the framework primitive and treat this package as a reference implementation.
Integration feasibility. Mechanically trivial: composer require, Laravel package auto-discovery, and a field class referenced in a Resource schema. No data migration is required if the underlying pivot tables already follow Laravel conventions (alphabetical singular table names, *_id foreign keys). Where the schema deviates, expect to subclass or configure table/key names explicitly.
Two diligence flags before any composer require:
wotz/filament-belongs-to-many but the repository lives under wotzebra/. This is benign in most cases but must be confirmed as the same publisher; it is exactly the pattern supply-chain review is meant to catch.Technical risk. High, and almost entirely adoption risk rather than code risk. Zero stars, zero dependents, and zero downstream consumers mean:
The second-order risk is Filament major-version coupling. Filament has shipped breaking changes across v2→v3→v4 in form component lifecycle, state hydration, and schema APIs. A zero-adoption package has no incentive and no pressure to track those releases. If we adopt, we own that upgrade path.
The MIT license is the strongest signal here: it converts "adopt a dependency" into "adopt a code sample we may vendor and maintain," which is a materially different — and much more defensible — posture.
Key questions.
composer.json, and is the constraint a caret range broad enough to admit an untested major?BelongsToMany field does not? Ask for a concrete side-by-side of a pivot-attribute-editing scenario.sync() or syncWithoutDetaching() on save, and how does it handle pivot columns not present in the form? Silent pivot-attribute loss on save is the single most common HABTM bug class.Stack fit. Confirm the three-way constraint triangle before anything else: PHP version, Laravel version, and Filament major version. All three are coupled — Filament 3 requires Livewire 3, which pins PHP and Laravel floors. If our standard application baseline is Filament 4, and this package only declares Filament 3, the integration is dead on arrival regardless of code quality. Produce a compatibility matrix with one row per supported combination and mark unverified cells explicitly rather than assuming forward compatibility.
Migration path. There is no migration in the database sense — the package is stateless and reads/writes through existing Eloquent relations. The migration that matters is UI migration: any Resource currently using the native BelongsToMany field would swap to the package's field class. That is a one-line schema change per form, which is good for reversibility but also means the cost of trying it is near zero — which argues for a spike rather than a business case.
If the pivot tables are non-conventional (custom table names, non-standard foreign keys, UUID/ULID keys, or composite keys), budget for configuration or subclassing. HABTM libraries routinely assume integer auto-increment keys and break quietly on UUID pivots.
Compatibility. Treat Filament version support as the gating criterion. Secondary compatibility concerns: Livewire version alignment, and whether the field's JavaScript/Alpine assets are registered through Filament's asset pipeline or shipped as raw includes (the latter is fragile across upgrades and across multiple panels). Also verify behavior under multiple Filament panels, since service-provider asset registration is a frequent source of cross-panel regressions.
Sequencing.
Maintenance. This is the dominant cost and it is entirely ours. With zero dependents and a single maintainer, assume we are the de facto maintainers from day one. Budget realistically: a vendored field component is small enough to own (likely a few hundred lines), but every Filament upgrade becomes a scheduled task rather than a background concern. If we take it as a Composer dependency instead of vendoring, we inherit an unpatched dependency risk with no remediation path other than forking under time pressure — strictly worse than forking deliberately now.
Support. No community, no Stack Overflow corpus, no third-party tutorials, no vendor SLA. Internal support means reading the source. Mitigate by requiring the adopting engineer to write a short internal runbook covering the field's state lifecycle and pivot-save semantics before merge. That documentation is the actual deliverable; the code is incidental.
Scaling. Two scaling axes matter. Option-set size: a multi-select that hydrates every related record degrades badly past a few hundred options and will show up as slow form renders long before it shows up as an error. Confirm search/pagination support or cap the usable relation size. Query behavior: verify eager loading of the relation on the index/list page, or the table column will introduce N+1 queries proportional to rows × related records — a classic silent performance cliff that passes code review and fails in production.
Failure modes. Rank by severity:
saveRelationshipsUsing uses sync() without preserving pivot columns, editing a record can strip attributes the form never surfaced. Highest severity, lowest visibility — data disappears quietly. Test explicitly.Ramp-up. Low for a Laravel-fluent engineer: half a day to understand the field, one to two days to be able to modify it safely. The real ramp-up cost is Filament internals — form component lifecycle, state hydration, and the saveRelationshipsUsing hook — which is transferable knowledge regardless of this package's fate. That is the strongest argument for running the spike even if the outcome is "use native": the team leaves with a durable understanding of how Filament persists relationships, which pays off on every future custom field.
Bottom line. At 15/100 with zero adoption, this should not enter the dependency graph as-is. The recommended posture is a one-to-two-day spike to extract the pattern, followed by either vendoring the component under MIT into internal namespace (if the capability gap over native BelongsToMany is real and demonstrable) or discarding it in favor of core. The asymmetry is favorable either way: the spike is cheap, the code is permissively licensed and small, and the alternative — pinning a production admin panel to an unmaintained zero-adopter package — is the only option that carries genuine downside.
How can I help you explore Laravel packages today?