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 Relation Manager Tabs Laravel Package

asignua/filament-relation-manager-tabs

A Filament plugin that adds tabs to relation managers, letting you group multiple relationship tables into one clean tabbed interface on resource pages. Simple setup and native Filament styling.

View on GitHub
Deep Wiki
Context7

Getting Started

This package lets you render a Filament relation manager inside the Edit/View page itself, as a tab or a collapsible section, instead of a separate "Manage related records" page. It is a UI-composition package, not a data-layer one — your RelationManager classes stay ordinary Filament relation managers.

  1. Check your Filament version first. v1.1.1 requires Filament 5.6+. The tab calls deferBadge() (5.3+) and isBadgeDeferred() (5.6+). If you are on 5.0–5.5, every Edit/View page with a tab will crash until you either upgrade Filament or pin this package to 1.1.0.
  2. Install and let Composer resolve the constraint:
    composer require <vendor>/<package>
    php artisan filament:assets   # if the package ships assets
    
  3. Look first at your existing page/resource class (EditRecord, ViewRecord, or the resource's getRelations()), then the package README's tab example. There is no config file to learn — the surface area is the tab/section object plus the relation manager class you already have.
  4. First use case: take a CommentsRelationManager that currently lives behind a header action button, and surface it as a tab on EditPost so editors can manage comments without losing their place in the post form.

Implementation Patterns

Tab on a resource page. Register the tab on the page, pointing at an existing relation manager:

public function getRelationManagers(): array
{
    return [
        RelationManagerTab::livewire(CommentsRelationManager::class)
            ->label('Comments'),
        // ...or via the resource's tab list, depending on your version
    ];
}

Section variant. The same manager can be embedded as an expandable section rather than a tab — useful when the form is short and you want related records visible without a click. Both tab and section share state through ManagerReference::memoized(), so mixing the two for the same manager does not double the badge/tooltip work.

Passing context into the embedded component. The factory sets record to null by default (fix for the attribute leak). Override it when the manager genuinely needs a different owner record:

RelationManagerTab::livewire(CommentsRelationManager::class)
    ->data(['record' => $this->getRecord()->parentPost()]);

A caller-supplied data(['record' => ...]) still wins over the default, so this is the supported extension point.

Badges and tooltips as cheap summaries. Drive the tab label from the manager's state (->badge(...), ->badgeColor(...), ->tooltip(...)) so users see "Comments (12)" without opening the tab. In v1.1.1 the tab resolves badge/badge colour/tooltip once per record per page, shared between the nav and both dropdown loops — so writing a badge callback that runs a counting query is now affordable, though still worth caching on large relations.

Custom tab keys. Give tabs a stable key: when you need to address them from tests, anchors, or activeTab state:

RelationManagerTab::livewire(CommentsRelationManager::class)->key('comments')

Keep keys plain ASCII alphanumerics and _/-. The rule that actually ships (and now matches the README) rejects only: empty strings, backslash, quote, backtick, angle bracket, control characters — and, as of 1.1.1, &.

Daily workflow shape. Typical use is: relation manager stays the single place where table/filters/actions live; the page exposes it as a tab; tests use Filament's table-testing helpers on the relation manager class directly rather than through the page. You rarely need to touch the tab object after registration, which is the point — no duplicated table definitions.

Gotchas and Tips

  • The Filament 5.6 floor is a hard runtime break, not a soft hint. If your composer.json still allows filament/filament: ^5.0, a composer update can produce a working install that fatals at render time on every Edit/View page carrying a tab. Pin or upgrade deliberately, and add "filament/filament": "^5.6" to your own constraints so CI catches it.
  • The & behaviour change is a real BC break for custom keys. Previously accepted, now throws InvalidArgumentException. The reason is instructive: the key lands in the tab button's Alpine attribute, and the browser decodes character references (a&lt;b → a<b), so the tab opened empty and the mismatch was invisible. Any key containing & was already subtly broken — audit keys for HTML-entity characters generally, not just &.
  • Don't pass a record "just in case." The v1.1.1 bug was exactly this: Filament's Livewire component always injects 'record', the manager has no such property, and a Livewire that forwards unknown parameters as HTML attributes printed the model JSON into the DOM — one " in a field and the attribute broke, leaking raw text into the hidden panel. If you are on ≤1.1.0 and see stray JSON or mangled markup near a tab, that is the cause; upgrade.
  • Only override record when the manager needs a non-obvious owner. Since the default is now null, a manager that assumes $this->getOwnerRecord() is populated will need the explicit ->data(['record' => ...]).
  • Enter-key handling is scoped. The embedded component installs a keydown.enter guard limited to inputs owned by the outer form, so pressing Enter in the manager's search box, filter input, or an inline-edit cell no longer submits (and half-saves) the surrounding Edit form. Test this after upgrading if you added custom inputs inside the tab's header/footer — the guard is form-ownership-based, and an input that sits in the form's DOM but is rendered by your own Blade can fall on the wrong side of the check.
  • Memoization is per record per page render. Badge, colour and tooltip are computed once and reused by nav + dropdowns. That is great for query count, but it means mutating state mid-request (e.g. an action that changes the related count and then re-renders the nav in the same cycle) may show a stale badge. Trigger a fresh render cycle rather than expecting the memo to invalidate.
  • Debugging checklist for "tab renders empty": (1) key contains a rejected character; (2) Filament older than 5.6; (3) the relation manager's $relationship doesn't match the page's record; (4) the manager needs an owner record you did not pass. All four present as a blank or silently missing panel rather than an exception.
  • Extension points are intentionally narrow — ->data([...]) for owner context, ->key() for identity, and the badge/label/tooltip callbacks. Everything else belongs in the relation manager, which keeps the embedded view identical to the standalone one; resist reimplementing table setup on the tab object.
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.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
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