Relation Manager Tabs
CommunityRender relation managers as ordinary tabs of the record form, so an edit or view page has exactly one row of tabs.
Author:
asign
Package health
BetaAutomated checks of this plugin's Composer package
15 checks
- Passed: GitHub Actions pinned to SHA
- Skipped: GitLab CI includes pinned to SHA
- Passed: Open security advisories
- Passed: Dependabot PR responsiveness — No open Dependabot PRs.
- Skipped: Renovate MR responsiveness
- Passed: Dependabot or Renovate configured
- Passed: Dependency update cooldown configured
- Passed: Provides a security policy
- Passed: Abandoned or archived — No consulted source marks the package abandoned (packagist, github).
- Passed: Commit and release recency — Active: last commit 0 days ago; last release 0 days ago.
-
Passed:
composer.lock not committed by library
—
composer.lockis absent from the released dist archive. - Passed: Dist archive is lean
-
Passed:
Current Laravel version supported
—
Package dependencies resolve together with current Laravel
13.0. -
Passed:
Current PHP version supported
—
Constraint
^8.3supports current PHP8.5. - Skipped: Current Symfony version supported
filament/
namespace. Review the source and install at your own risk. Found
malware or an unresolved security issue the author won't
address?
Report it
.
Documentation
- Screenshots
- Requirements
- Installation
- Usage
- Lazy loading
- Collapsible sections
- Why not hasCombinedRelationManagerTabsWithContent()
- Gotchas
- AI agents
- Testing
- Changelog
- License
Render a Filament relation manager as an ordinary tab of the record form. The edit page then has exactly one row of tabs: the form's own tabs and your relation managers side by side.
- Screenshots
- Requirements
- Installation
- Usage
- Lazy loading
- Collapsible sections
- Why not
hasCombinedRelationManagerTabsWithContent() - Gotchas
- AI agents
- Testing
#Screenshots
One row of tabs — the form's own tabs and the relation managers side by side, with their badges:

The same page in dark mode:

The form tabs work as usual:

For comparison, stock Filament — the managers get a second row of tabs under the form:

A RelationManagerSection starts collapsed - nothing is loaded:

Expanding it mounts the manager on demand:

#Requirements
- PHP 8.3+
- Laravel 12 or 13
- Filament 5
#Installation
composer require asignua/filament-relation-manager-tabs
There is no service provider and nothing to register: it is a schema helper, not a panel plugin.
#Usage
Put RelationManagerTab::make() into the same Tabs component as the rest of the form, and
return an empty array from getRelations():
use Asignua\FilamentRelationManagerTabs\RelationManagerTab;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Schema;
class TeamResource extends Resource
{
public static function form(Schema $schema): Schema
{
return $schema->components([
Tabs::make('Tabs')
->persistTabInQueryString()
->tabs([
Tabs\Tab::make('Details')->schema([
TextInput::make('name')->required(),
]),
RelationManagerTab::make(PostsRelationManager::class),
RelationManagerTab::make(
MembersRelationManager::class,
label: 'People',
icon: 'heroicon-o-users',
key: 'people',
),
])
->columnSpanFull(),
]);
}
// The managers live in the form's tabs — never return them here as well.
public static function getRelations(): array
{
return [];
}
}
make() takes the manager class, an optional label (default: the manager's getTitle()), an
optional icon (default: its getIcon()) and an optional tab key. Everything else comes from the
manager, exactly as on a stock relation-manager tab: the icon position (getIconPosition()), the
badge (getBadge(), getBadgeColor(), getBadgeTooltip(), and $isBadgeDeferred) and the
visibility (canViewForRecord()). The manager receives the same data as in the stock block,
including the page's activeLocale when the page uses a locale switcher (spatie-translatable).
To pass properties to the manager, give make() a configuration instead of a class name, as in
stock getRelations():
RelationManagerTab::make(PostsRelationManager::make(['status' => 'draft']));
RelationGroup (several managers under one stock tab) is not supported: give each manager its
own tab. A manager's overridden getTabComponent() is not used either; the tab is built from the
static getters listed above.
It works the same way on View pages: put the tab into the Tabs of the infolist (or of the
form the View page shows).
Several managers at once, with default labels, icons and keys:
Tabs::make('Tabs')->tabs([
Tabs\Tab::make('Details')->schema([/* ... */]),
...RelationManagerTab::many([
PostsRelationManager::class,
MembersRelationManager::class,
]),
]);
#Lazy loading
Filament relation managers are already lazy by default (RelationManager::$isLazy = true):
Livewire renders a placeholder and mounts the manager when it scrolls into view. A tab panel
that is not active and a section that is collapsed are display: none, so they never intersect —
nothing is mounted or queried until the tab is opened / the section expanded. (Older Filament
discussions asking for this predate that default.) The lazy: argument lets you override the
manager per placement:
RelationManagerTab::make(PostsRelationManager::class); // follow the manager (default)
RelationManagerTab::make(PostsRelationManager::class, lazy: true); // force lazy, even if $isLazy = false
RelationManagerTab::make(PostsRelationManager::class, lazy: false); // mount eagerly with the page
RelationManagerTab::many([...], lazy: true);
A lazy manager mounts when its placeholder enters the viewport, so a section expanded below the fold loads when you scroll to it, not at the click.
Note that the tab badge is computed by the parent page, so a getBadge() that runs a query
still runs on the initial render. Set $isBadgeDeferred = true on the manager if that matters: the
tab then renders without the badge and fetches it in a separate request, as a stock tab does. A
section has no such request, so in a section the badge is always computed with the page (once).
#Collapsible sections
When tabs are the wrong layout, put the manager in a collapsible Section:
use Asignua\FilamentRelationManagerTabs\RelationManagerSection;
$schema->components([
Section::make('Details')->schema([/* ... */]),
RelationManagerSection::make(PostsRelationManager::class), // collapsed + lazy
RelationManagerSection::make(
MembersRelationManager::class,
label: 'People',
icon: 'heroicon-o-users',
collapsed: false,
lazy: false,
key: 'people',
),
]);
The heading, icon, badge, badge colour and badge tooltip come from the manager like on a tab (a
Section has no icon position, so getIconPosition() does not apply); the section is hidden on Create
and when canViewForRecord() is false. By default it starts collapsed and lazy (the
manager is mounted on first expand). It returns a regular Filament\Schemas\Components\Section,
so ->columnSpanFull(), ->description(), ->persistCollapsed() and so on still work. The
same slug-key and "empty getRelations()" rules apply.
#Why not hasCombinedRelationManagerTabsWithContent()
Stock Filament draws the form with its own Tabs, and the related records in a separate block
under it — with two or more managers, a second row of tabs. Filament's built-in
hasCombinedRelationManagerTabsWithContent() does not remove that second level: it wraps the
whole form into one tab, so your form tabs just sink one level down. This helper hangs the
manager into the form's own Tabs, which is the only way to get a single row.
#Gotchas
- No tab on the Create page. A relation manager needs an owner record, and on a create page the record does not exist yet. The tab is hidden until it does (the same condition Filament uses to hide managers on create).
- The tab key is a slug, and it must stay one. Filament pastes the key into Alpine as a bare
string in the tab button (
x-on:click="tab = '<key>'") but double-escaped in the panel. A key holding a class name (backslashes) makes the two diverge:\F,\E,\Ubecome JS escape sequences in the button, and the tab opens empty, with no console error. The default key is the slug of the class basename (relation-manager-postsrelationmanager). Pass your ownkey:when you usepersistTabInQueryString(), because the key ends up in the URL. A custom key must not be empty and must not contain a backslash, a quote (',"), a backtick,<,>or a control character; such a key throwsInvalidArgumentException. A backslash opens an empty tab, and Filament silently strips the rest from keys, so the key in the DOM and the URL would not be yours. Spaces,:, non-ASCII letters and the like are fine. - Two managers with the same class basename (
Blog\PostsRelationManagerandShop\PostsRelationManager) get the same default key. On one page, passkey:to at least one of them, or the tabs (or section ids) collide. - Do not also return the manager from
getRelations()(stock Filament renders whatever it returns as a block under the form). A manager left registered there is rendered twice: once as your tab or section and once as the stock block. - Nesting inside the edit page
<form>is fine. Filament draws the action modal only after the action is mounted (a Livewire DOM patch), so the nested form survives. You do not needhasFormWrapper(): false.
#AI agents
The package ships Laravel Boost guidelines
(resources/boost/guidelines/core.blade.php): with Boost installed, php artisan boost:install
picks them up, so your coding agent knows the rules above.
#Testing
composer test # PHPUnit (Orchestra Testbench)
composer analyse # Larastan
composer format # Pint
#Changelog
See CHANGELOG.md.
#License
MIT. See LICENSE.md.
The author
asign is a small web-dev company from Lviv, Ukraine. We build business applications on Laravel and Filament — CRMs, automation systems for standard and non-standard business processes, booking and content management systems, including our own Filament-based CMS. We open-source the parts that prove useful beyond a single project
From the same author
SEO Files
Generate and edit sitemap.xml with hreflang and a sitemap index for large sites, robots.txt and llms.txt / llms-full.txt from your Filament panel
Author:
asign
Activity Log Plus
An add-on for spatie/laravel-activitylog that records per-language diffs, skips phantom changes, groups each action into one entry and adds a History button to every record.
Author:
asign
Chat
Team chat for your panel: direct messages and groups, @mentions, reactions, read receipts, a pinnable slide-over, and messages that link to your panel's records.
Author:
asign
Image Meta
Per-file alt text, a decorative flag, caption, title, and a focal point for Filament 5's own FileUpload and SpatieMediaLibraryFileUpload: one wrapper around the upload you already have, no media system to adopt.
Author:
asign
Featured Plugins
A selection of plugins curated by the Filament team
Compact Theme
A theme that makes data-heavy panels easier to scan by fitting more useful information on each screen.
Filament
Custom Fields
Eliminate custom field migrations forever. Let your users create and manage form fields directly in Filament admin panels with 20+ built-in field types, validation, and zero database changes.
Relaticle
Spotlight Pro
Browse your Filament Panel with ease. Filament Spotlight Pro adds a Spotlight like Command Palette to your Filament Panel.
Dennis Koch