Skip to content

Relation ​

The relation field picks records through a model relation, with the options read from the related model. A relation to one record renders as a dropdown, and a relation to many as a checkbox list.

A belongsTo, hasOne or morphOne relation renders as a dropdown, and saving sets the relation to the picked record. The field name is the relation name, here the user relation to the User model.

yaml
fields:
    user:
        label: Owner
        type: relation
        nameFrom: name
        emptyOption: No owner
        defaultSort:
            column: name
            direction: asc
        comment: A belongsTo relation renders as a dropdown of users, sorted by name.
php
/**
 * @var array<string, mixed> belongsTo relations, the owner, reviewer, group and category picked by the relation widgets.
 */
public $belongsTo = [
    'user' => User::class,
    'reviewer' => User::class,
    'primary_group' => UserGroup::class,
    'primary_category' => Category::class,
];

nameFrom names each option from a column of the related model, name by default. It can also name an accessor, though the field then loads every record to read it. emptyOption adds a first option that saves no record, so the relation can be cleared. defaultSort orders the options by a column and a direction, which is desc when left out.

Checkbox list ​

A belongsToMany, morphToMany, morphedByMany or hasMany relation renders as a checkbox list, and saving syncs the relation to the checked records. Unchecking every box clears it.

yaml
fields:
    groups:
        label: Groups
        type: relation
        scope: withoutGuest
        emptyOption: There are no groups to pick from.
        comment: A belongsToMany relation renders as a checkbox list, leaving out the guest group.
php
/**
 * @var array<string, mixed> belongsToMany relations, the groups, categories and labels picked by checkbox and tag lists.
 */
public $belongsToMany = [
    'groups' => [UserGroup::class, 'table' => 'sample_user_group'],
    'categories' => [Category::class, 'table' => 'category_sample'],
    'labels' => [Tag::class, 'table' => 'sample_tag'],
];

scope narrows the options with a model scope on the related model, here leaving out the guest group. The scope is called with the query and the record being edited, and scope also takes a static Class::method with the same arguments. On a checkbox list, emptyOption is the message shown when there are no options.

php
/**
 * Exclude the built-in "guest" group. Used by the relation fields so a user
 * cannot be assigned to the guest pseudo-group.
 */
public function scopeWithoutGuest($query)
{
    return $query->where('code', '<>', 'guest');
}

Shaping the options ​

select builds each option's label in SQL, in place of nameFrom. conditions filters the options with a raw SQL where clause, and a :column placeholder in it reads that attribute of the record being edited, quoted, such as country = :country. Both are adapted to the database in use, so concat() also works on SQLite and PostgreSQL.

excludeFrom names an attribute of the record whose keys are left out of the options, here the saved owner's user_id, so the reviewer cannot be the owner. Without it, a relation to the same model, such as a category's parent, leaves out the record being edited.

yaml
fields:
    reviewer:
        label: Reviewer
        type: relation
        select: concat(name, ' (', email, ')')
        conditions: email_verified_at is not null
        excludeFrom: user_id
        emptyOption: No reviewer
        comment: Verified users other than the saved owner, labelled with their email.

conditions takes the place of scope, so when both are set only conditions applies. With neither, the relation's own constraints filter the options.

Tree dropdown ​

When the related model is a tree, using the NestedTree or SimpleTree trait, the options follow the tree, and each child is indented under its parent. When scope or conditions leaves out a parent, its children still show.

yaml
fields:
    primary_category:
        label: Primary Category
        type: relation
        emptyOption: No category
        comment: Categories form a nested tree, so child options are indented under their parent.
php
/**
 * Category is a post category stored as a nested set tree, so the list structure can display, nest and reorder it.
 */
#[Fillable(['name', 'slug', 'description', 'parent'])]
class Category extends Model
{
    /** @use HasFactory<CategoryFactory> */
    use HasFactory;
    use NestedTree;
    use Validation;

Tree checkbox list ​

A belongsToMany relation to a tree nests the checkboxes the same way, with each child under its parent.

yaml
fields:
    categories:
        label: Categories
        type: relation
        comment: The same tree as a checkbox list, with child categories nested under their parent.

Quick create ​

quickCreate adds a Create New option at the top of the dropdown. Picking it opens a popup with a form for the related model, and saving the popup creates the record and selects it in the dropdown. The new record is stored at once, while the field's own value waits for the form's Save like any other change.

yaml
fields:
    primary_group:
        label: Primary Group
        type: relation
        scope: withoutGuest
        quickCreate:
            fields: ~/resources/ui/reference/form-widgets/relation/quick-create-form.yaml
            popupSize: small
        comment: Pick Create New to add a group in a popup, which is then selected here.

quickCreate takes the path to the popup's fields YAML, or these options:

OptionDescription
fieldsthe popup form's fields, as the path to a fields YAML file.
titlethe popup title. Default: New :name.
optionTextthe dropdown option that opens the popup. Default: - Create New :name -.
contextthe context the popup form renders in. Default: quickcreate.
popupSizethe popup width, such as small or large. Default: the popup's own size.

:name stands for the field label. The popup form renders in its own context, so a field limited to other contexts stays out of it: here the members field sets context: [create, update], and the popup shows only the group's name, code and description.

yaml
fields:
    name:
        label: Name
        span: left
        autoFocus: true
    code:
        label: Code
        span: right
    description:
        label: Description
        type: textarea
        size: tiny
    users:
        label: Members
        type: taglist
        nameFrom: name
        context: [create, update]

Quick create works on a dropdown only, and is not offered in preview or read-only mode. When the field has no emptyOption or placeholder, it gains a Select placeholder, so Create New is never picked by default, and saving while Create New is still picked clears the relation.

Read-only ​

readOnly shows the picked option as text, and still posts its key from a hidden input. This copy reads the owner through valueFrom, and its name starts with an underscore, which keeps it out of the saved data.

yaml
fields:
    _owner_readonly:
        label: Owner
        type: relation
        valueFrom: user
        readOnly: true
        comment: Shows the owner as text and still posts its key.

Disabled ​

disabled locks the options, and the field is left out of the save.

yaml
fields:
    groups_disabled:
        label: Groups
        type: relation
        valueFrom: groups
        disabled: true
        comment: Shows the groups as disabled checkboxes, which are never saved.

Saving on a plain Eloquent model ​

The field reads its options and the current selection from Eloquent relation methods as well as from Rain's relation arrays, so the Showcase's User model, a stock Eloquent model, declares its relations the usual Laravel way:

php
/**
 * The primary group this user belongs to (belongs-to relation, rendered by
 * the "Primary Group" relation field on the form).
 */
public function primary_group(): BelongsTo
{
    return $this->belongsTo(UserGroup::class, 'primary_group_id');
}

/**
 * The secondary groups this user belongs to (many-to-many relation,
 * rendered by the "Secondary Groups" relation field on the form).
 */
public function groups(): BelongsToMany
{
    return $this->belongsToMany(UserGroup::class, 'user_user_group');
}

Saving is where the two differ. An October Rain model takes the posted keys as attribute values and sets its relations when it saves. A plain Eloquent model needs the relations set in your save handler, as the Showcase author form does: it maps the dropdown onto the foreign key, and syncs the checkbox list once the record is saved.

php
// The belongs-to field posts under its relation name, so it maps onto the foreign key
if (array_key_exists('primary_group', $data)) {
    $data['primary_group_id'] = $data['primary_group'] ?: null;
}

// The many-to-many ids sync once the record exists, and an empty selection clears them
$hasOtherGroups = array_key_exists('groups', $data);
$otherGroups = ($data['groups'] ?? null) ?: [];
unset($data['groups'], $data['primary_group'], $data['password_confirmation']);

$user->fill($data)->save();

if ($hasOtherGroups) {
    $user->groups()->sync($otherGroups);
}

An empty selection posts an empty string, while a missing key means the field was not on the form, so sync only when the key is present. The same goes for a tag list or a record finder on a relation. Models covers the rest of what plain Eloquent supports.

Options ​

OptionDescription
nameFromthe related model's column, or accessor, that labels each option. Default: name.
selectan SQL expression that labels each option, in place of nameFrom.
emptyOptionon a dropdown, a first option that saves no record. On a checkbox list, the message shown when there are no options.
scopea model scope on the related model, or a static Class::method, that narrows the options. Also accepted as modelScope.
conditionsa raw SQL where clause that narrows the options, with :column placeholders from the record, used in place of scope.
defaultSortthe option order, as a column and a direction, or a column name alone for descending order.
excludeFroman attribute of the record whose keys are left out of the options.
quickCreateadds a Create New option to a dropdown, as a fields YAML path or the options above.
readOnlyshows the picked option as text and still posts its value. Default: false.

The field also takes the common field properties, such as placeholder, disabled and valueFrom.