Im Kundenfeld stehen dreimal „Müller“, die Suche nach „Anna“ liefert keinen Treffer und beim Bearbeiten ist kaum zu erkennen, welcher Datensatz ausgewählt wurde. Ein zusammengesetztes Label allein behebt das nicht. Tragfähig wird das Filament Select erst, wenn du das sichtbare Label aus dem Datensatz erzeugst und die durchsuchbaren Datenbankspalten ausdrücklich festlegst.
Warum ein einzelnes titleAttribute nicht ausreicht
Ein typisches Relationship-Feld beginnt mit einer knappen Konfiguration:
Select::make('customer_id')
->relationship('customer', 'last_name');Filament verwendet hier last_name als titleAttribute, also als Spalte für die sichtbare Bezeichnung. Das funktioniert technisch, ist für Personen aber oft zu wenig. Mehrere Kunden können denselben Nachnamen haben, und eine Suche nach dem Vornamen greift nicht automatisch auf eine weitere Spalte zu.
Naheliegend wäre ein Eloquent-Accessor wie full_name, der Vor- und Nachname verbindet. Ein Accessor ist jedoch keine echte Datenbankspalte. Wird er einfach als titleAttribute eingesetzt, kann Filament ihn nicht wie eine Spalte in der Relationship-Abfrage durchsuchen. Hübsche Ausgabe und funktionierende Suche sind zwei getrennte Aufgaben.
Der schnelle Fix für kombinierte Namen
Die Relationship bleibt für die Zuordnung zuständig. Über getOptionLabelFromRecordUsing() bestimmst du dagegen, wie ein gefundener Datensatz angezeigt wird. Mit searchable() legst du fest, welche echten Spalten Filament durchsuchen soll.
use App\Models\Customer;
use Filament\Forms\Components\Select;
Select::make('customer_id')
->label('Kunde')
->relationship(
name: 'customer',
titleAttribute: 'last_name',
)
->searchable(['first_name', 'last_name'])
->getOptionLabelFromRecordUsing(
fn (Customer $record): string => trim(
$record->first_name . ' ' . $record->last_name
)
);Die Aufgaben sind damit klar verteilt:
- relationship() verbindet das Formularfeld mit der Eloquent-Beziehung
customer. - titleAttribute bleibt eine vorhandene Datenbankspalte und liefert Filament eine verlässliche Grundlage für die Beziehung.
- searchable() ermöglicht die Suche über Vor- und Nachname.
- getOptionLabelFromRecordUsing() baut aus dem geladenen Customer-Datensatz die sichtbare Bezeichnung.
Das Label wird nicht nur für die Trefferliste gebraucht. Filament kann damit auch beim Öffnen eines bestehenden Datensatzes die bereits gespeicherte Auswahl verständlich darstellen. Du vermeidest dadurch den klassischen Schnellfix, der beim Anlegen funktioniert und im Bearbeitungsformular plötzlich nur noch eine ID oder ein unvollständiges Label zeigt.
Bei gleichen Namen braucht das Label ein Unterscheidungsmerkmal
„Anna Müller“ kann immer noch mehrfach vorkommen. Dann sollte das Label ein Merkmal enthalten, das die Person im Arbeitskontext eindeutig genug macht. Dafür eignen sich beispielsweise Kundennummer, Firma oder E-Mail-Adresse. Welche Angabe sinnvoll ist, hängt davon ab, was die bearbeitende Person tatsächlich kennt und suchen darf.
Mit der E-Mail-Adresse könnte das Feld so aussehen:
Select::make('customer_id')
->label('Kunde')
->relationship(
name: 'customer',
titleAttribute: 'last_name',
)
->searchable(['first_name', 'last_name', 'email'])
->getOptionLabelFromRecordUsing(
function (Customer $record): string {
$name = trim(
$record->first_name . ' ' . $record->last_name
);
return $record->email
? $name . ' · ' . $record->email
: $name;
}
);Die zusätzliche Spalte gehört auch in searchable(), wenn Nutzer nach ihr suchen sollen. Eine Information nur anzuzeigen, macht sie noch nicht durchsuchbar.
Prüfe außerdem, ob das zusätzliche Merkmal im Adminpanel sichtbar sein darf. Eine interne Kundennummer ist häufig unkritischer als eine private E-Mail-Adresse. Der praktischste Bezeichner ist nicht automatisch der datenschutzfreundlichste.
Warum options() mit allen Kunden meist der falsche Abkürzer ist
Ein verbreiteter Ausweg besteht darin, alle Kunden selbst zu laden und daraus Optionen zu bauen:
Select::make('customer_id')
->options(
Customer::all()->mapWithKeys(
fn (Customer $customer): array => [
$customer->id => trim(
$customer->first_name . ' ' . $customer->last_name
),
]
)
);Für wenige Datensätze kann das sichtbar funktionieren. Der Code lädt allerdings sämtliche Kunden bereits beim Aufbau des Formulars. Außerdem verlässt du die bestehende Relationship-Konfiguration und musst Einschränkungen wie Mandantenzuordnung oder fachliche Sichtbarkeit selbst korrekt nachbauen.
Das Relationship-Select mit gezielter Suche ist deshalb der bessere schnelle Fix. Es nutzt die bestehende Beziehung und lädt Treffer anhand einer Suchanfrage, statt vorsorglich den gesamten Kundenbestand in das Formular zu kippen. Ein preload() solltest du bei einer größeren Auswahl ebenfalls nicht reflexartig ergänzen, weil dadurch wieder Optionen vor der eigentlichen Suche geladen werden.
Die vier Prüfungen nach der Änderung
Ein Select ist erst repariert, wenn es beim Anlegen, Suchen und Bearbeiten konsistent funktioniert. Prüfe die Änderung deshalb mit realistischen Datenkonstellationen:
- Suche nach dem Vornamen: Ein Fragment wie „Ann“ muss den passenden Datensatz liefern.
- Suche nach dem Nachnamen: Auch häufige Nachnamen müssen erwartbare Treffer anzeigen.
- Öffne einen bestehenden Datensatz: Die gespeicherte Auswahl muss sofort mit dem vollständigen Label erscheinen.
- Prüfe unvollständige und doppelte Namen: Fehlende Vornamen dürfen keine merkwürdigen Leerzeichen erzeugen, identische Namen brauchen ein erlaubtes Unterscheidungsmerkmal.
Falls bestimmte Kunden aufgrund von Mandanten-, Rollen- oder Statusregeln nicht auswählbar sein dürfen, gehört auch das in den Test. Ein gut lesbares Label darf keine Datensätze sichtbar machen, die durch die fachliche Abfrage ausgeschlossen sein sollten.
Wann eine eigene Suchabfrage nötig wird
Die gezeigte Lösung reicht, solange die Suchbegriffe in Spalten der verknüpften Tabelle liegen. Schwieriger wird es, wenn das Label Informationen aus weiteren Beziehungen enthält, etwa den Firmennamen aus einer separaten Organisationstabelle. Dann kann ein vollständig angepasstes Suchverhalten erforderlich sein.
Dasselbe gilt für spezielle Normalisierungen, externe Kennungen oder Suchregeln, die mehrere Tabellen verbinden. In solchen Fällen solltest du die Suchabfrage bewusst definieren, statt immer mehr Darstellungslogik in das Label zu schieben. Das Label beantwortet die Frage „Was soll der Nutzer sehen?“. Die Abfrage beantwortet „Welche Datensätze darf er mit diesem Begriff finden?“.
Fazit
Wenn ein Filament Select nur Nachnamen zeigt oder den Vornamen nicht findet, brauchst du weder eine künstliche Datenbankspalte noch eine vorab geladene Liste aller Kunden. Lass die Relationship auf einer echten Spalte arbeiten, definiere Vor- und Nachname als Suchfelder und erzeuge das sichtbare Label direkt aus dem gefundenen Datensatz. So bleibt der Fix klein, während Anzeige, Suche und bestehende Auswahl zusammenpassen.
Häufige Fragen
Kurz beantwortet, damit du schneller einschätzen kannst, was für dein Projekt wichtig ist.
Wie zeige ich in einem Filament Select Vor- und Nachname an?
Nutze bei einem Relationship-Select getOptio
LabelFromRecordUsing(), um das Label aus first_name und last_name zusammenzusetzen. Die beiden echten Datenbankspalten gibst du zusätzlich an searchable() weiter.
Kann ich einen Eloquent-Accessor als titleAttribute verwenden?
Für die reine Ausgabe kann ein Accessor praktisch sein, er ist aber keine echte Datenbankspalte. Für die Relationship-Suche solltest du deshalb vorhandene Spalten angeben und das zusammengesetzte Label separat erzeugen.
Warum sollte ich nicht alle Kunden mit options() laden?
Damit werden alle Datensätze bereits beim Aufbau des Formulars geladen. Außerdem musst du fachliche Einschränkungen und Sichtbarkeitsregeln außerhalb der bestehenden Relationship selbst berücksichtigen.
Wie unterscheide ich Kunden mit identischem Namen?
Ergänze das Label um ein erlaubtes Merkmal wie Kundennummer, Firma oder E-Mail-Adresse. Soll danach gesucht werden können, muss die entsprechende Spalte ebenfalls in searchable() stehen.

