Fields are handlers for the model attributes. They are responsible for saving and displaying the given attribute of the resource model.
In some cases, the default fields are not enough, or don't provide the flexibility you need. In that scenario you can create your own custom fields easily. To generate a field easily you can call the following artisan command:
php artisan root:field Autocomplete
You can register fields in resources, extracts, actions and other fields by using the fields
method.
use Cone\Root\Fields\Text;
use Illuminate\Http\Request;
/**
* Define the fields for the resource.
*/
public function fields(Request $request): array
{
return [
Text::make(__('Title'), 'title'),
];
}
Field values are extracted from the given model's attributes. The attribute that is matching with the name of the field will be injected into the field when displaying or converting it into a form input.
It may happen that the attribute does not exists on the model that matches with the field, or its value needs to be converted before using it from the field. In that case you may define a default value resolver on your field instance:
use Cone\Root\Fields\Text;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
Text::make('Title')
->value(static function (Request $request, Model $model, mixed $value): string {
return is_null($value) ? 'foo' : $value;
});
Alternatively, you may do default value definitions on your model directly.
You may define custom value formatters for the field values. In that case you can easily define a format resolver with the format
method:
use Cone\Root\Fields\Number;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Number;
Number::make('Price')
->format(static function (Request $request, Model $model, mixed $value): string {
return Number::currency($value);
});
You may define custom value hydration logic on your field. To do so, use the hydrate
method passing a Closure
:
use Cone\Root\Fields\Number;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Number;
Number::make('Price')
->hydrate(static function (Request $request, Model $model, mixed $value): void {
$model->setAttribute('price', $value);
});
You may allow/disallow interaction with fields. To do so, you can call the authorize
method on the field instance:
use Illuminate\Http\Request;
$field->authorize(static function (Request $request, Model $model): bool {
return $request->user()->isAdmin();
});
You may show or hide fields based on the current resource view. For example, some fields might be visible on the index
page, while others should be hidden. You can easily customize the action visibility logic using the visibleOn
and hiddenOn
methods:
$field->visibleOn(['index', 'show']);
$field->hiddenOn(['update', 'create']);
You may define validation rules for your field instances. To do so, you can call the rules
, createRules
and updateRules
on the field instance:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
$field->rules(['string', 'required'])
->createRules(['unique:posts'])
->updateRules(static function (Request $request, Model $model): array {
return [Rule::unique('posts')->ignoreModel($model)];
});
You can pass an array
or a Closure
to the rule methods.
The value passed to the rules
method will be present for both creating and updating models.
On index or extract views you may want to filter the results by sorting or searching any desired fields. To do so you can call the sortable
and the searchable
methods on your field instance:
$field->sortable();
$field->sortable(false);
$field->searchable();
$field->searchable(false);
The Boolean
field is typically a handler for boolean
model attributes:
Don't forget to cast you model attribute as a boolean
.
$field = Boolean::make(__('Enabled'), 'enabled');
The Checkbox
field is typically a handler for json
model attributes (array of values):
Don't forget to cast you model attribute as a json
or array
.
$field = Checkbox::make(__('Permissions'), 'permissions')
->options([
'view' => 'view',
'create' => 'Create',
'update' => 'Update',
'delete' => 'Delete',
]);
You may also pass a Closure
to customize the resolution logic of the options, see the Select
field.
The Color
field is typically a handler for color
model attributes:
$field = Color::make(__('Background Color'), 'bg_color');
You may use the hex_color
validation rule for this field. By default no rules attached.
The Date
field is typically a handler for date
or datetime
model attributes:
$field = Date::make(__('Expires at'), 'expires_at');
You may use the date
validation rule for this field. By default no rules attached.
You can also apply modifiers on a Date
field:
// Adds/removes a time handler for the field
$field->withTime();
$field->withTime(false);
// Sets the displayed timezone for the input
// Note, the value is saved to the database in the app's timezone (normally UTC)
$field->timezone('Europe/Budapest');
// Adds the "step" HTML input attribute
$field->step(1);
// Adds the "min" HTML input attribute
$field->min('2024-01-01');
$field->min(now()->addDays(7));
// Adds the "max" HTML input attribute
$field->max('2024-01-01');
$field->max(now()->addDays(7));
The Dropdown
field is turbo-charded version of the Select
field. You may checkout its documentation.
$field = Dropdown::make(__('Tags'), 'tags')
->options([
'bug' => 'Bug',
'info' => 'Info',
'question' => 'Question',
]);
The Editor
field is typically a handler for text
model attributes (HTML). The Editor
field is a Tiptap editor combined with alpine:
$field = Editor::make(__('Content'), 'content');
The root.php
config file contains the default configuration for each editor instance. You may edit the config file or you can also customize the configuration per instance:
$field->withConfig(static function (array $config): array {
return array_merge($config, ['autofocus' => true]);
});
It is also possible using the Media Library with the Editor
field to insert existing media files or upload new ones:
$field->withMedia();
// or you may customize the media field
$field->withMedia(static function (Media $media): void {
$media->withRelatableQuery(static function (Request $request, Builder $query): Builder {
return $query->where('user_id', $request->user()->getKey());
});
});
You can also apply modifiers on a Editor
field:
// Sets the editor height
$field->height('300px');
The Email
field is typically a handler for email
model attributes:
$field = Email::make(__('Billing Email'), 'billing_email');
You may use the email
validation rule for this field. By default no rules attached.
The Fieldset
field a handler for grouped sub-fields. Also, it renders its fields into a <fieldset>
HTML element:
$group = Fieldset::make(__('Billing Fields'), 'billing')
->withFields(static function (): array {
return [
Text::make(__('Name'), 'billing_name'),
Email::make(__('Email'), 'billing_email'),
];
});
The Hidden
field is eqvivalent to a simple Field
instance, however it automatically gets a type="hidden"
HTML attribute, that makes it uneditable:
$field = Hidden::make(__('Token'), 'token')
->default(fn (): string => Str::uuid());
The ID
field is typically a handler for the model's primary key. It can be an incremental numeric ID or a UUID as well.
$field = ID::make();
The Number
field is typically a handler for numeric
model attributes:
$field = Number::make(__('Age'), 'age');
You can also apply modifiers on a text field:
// Adds the "size" HTML input attribute
$field->size(40);
// Adds the "min" HTML input attribute
$field->min(40);
// Adds the "max" HTML input attribute
$field->max(40);
// Adds the "step" HTML input attribute
$field->step(1);
The Radio
field is similar to Checkbox
, however only single values are allowed, unlike in the case of Checkbox
, where array of values are being stored:
$field = Radio::make(__('Role'), 'role')
->options([
'admin' => 'Admin',
'editor' => 'Editor',
'member' => 'Member',
]);
You may also pass a Closure
to customize the resolution logic of the options, see the Select
field.
The Range
field is typically a handler for numeric
model attributes:
$field = Range::make(__('Points'), 'points')
->min(0)
->max(10);
The Repeater
field similar to the Fieldset
field, however the sub-fields are repeatable:
$field = Repeater::make(__('Books'), 'books')
->withFields(static function (): array {
return [
Text::make(__('Author'), 'author'),
Text::make('ISBN'),
];
});
You may also set a maximum number of repeatable elements using the max
method:
$field->max(4);
The Select
field is similar to the Checkbox
, Dropdown
and Radio
fields, in fact the Select
field is their parent class:
$field = Select::make(__('Tags'), 'tags')
->options([
'bug' => 'Bug',
'info' => 'Info',
'question' => 'Question',
]);
You may make the field to nullable
which means an extra empty option is available in the options:
$field->nullable();
// or
$field->nullable(false);
You can also apply modifiers on Select
field:
// Adds the "size" HTML input attribute
$field->size(10);
// Adds the "multiple" HTML input attribute
$field->multiple();
// or
$field->multiple(false);
You may also pass a Closure
to customize the resolution logic of the options:
use App\Category;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
$field = Select::make(__('Category'), 'category')
->options(static function (Request $request, Model $model): array {
return match (true) {
$request->user()->isAdmin() => Category::query()->pluck('name', 'id')->all(),
default => $request->user()->categories()->pluck('name', 'id')->all(),
};
});
The Slug
field is typically a handler for auto-generating URL friendly values of specified model attributes:
$field = Slug::make(__('Slug'), 'slug')
->from(['name']);
You may also want to have unique slugs, which means when a slug is already exists in the database table, the new slug will get an incremented numeric suffix to avoid conflicts:
$field->unique();
// unique-slug
// unique-slug-1
// unique-slug-2
// etc...
You may also want to customize the slug separator:
$field->separator('_');
The default slug separator is -
.
You may also want to completely customize the slug generation logic:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
$field->generateUsing(static function (Request $request, Model $model, string $value): string {
return sprintf('%s-%s', $value, Str::random(5));
});
By default the Slug
field is generating slug only when creating the model. If you want to run the generation logic when the model is being updated as well, you may call the always
method:
$field->always();
The Text
field is typically a handler for string
model attributes:
$field = Text::make(__('Title'), 'title');
You can also apply modifiers on Text
field:
// Adds the "size" HTML input attribute
$field->size(40);
// Adds the "minlength" HTML input attribute
$field->minlength(40);
// Adds the "maxlength" HTML input attribute
$field->maxlength(40);
The Textarea
field is typically a handler for longer string
model attributes:
$field = Textarea::make(__('Bio'), 'bio');
You can also apply modifiers on a text field:
// Adds the "rows" HTML input attribute
$field->rows(20);
// Adds the "cols" HTML input attribute
$field->cols(100);
The URL
field is typically a handler for url
model attributes:
$field = URL::make(__('GitHub Profile'), 'gh_profile');
Relation fields are representing Eloquent relation definitions on the resource models. Relation fields are highly customizable and provide a nice and detailed API.
Searchable & Sortable Columns
When using the searchable
and sortable
methods on the relation fields the target columns should be defined.
$field->searchable(columns: [
'id', 'name',
]);
$field->sortable(column: 'name');
Customizing the Query
You may customize the relatable model's query. This is possible defining global scopes per field type or locally on the field definition.
You may apply global customization using the scopeQuery
static method:
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
Media::scopeQuery(function (Request $request, Builder $query, Model $model): Builder {
return $query->where('user_id', $request->user()->getKey());
});
This will apply the query resolution logic to all Media
fields.
You may apply local customization using the withRelatableQuery
method:
$field->withRelatableQuery(function (Request $request, Builder $query, Model $model): Builder {
return $query->where('user_id', $request->user()->getKey());
})
Formatting
You may format the relatable models. By default the ID is displayed, but you can customize the format easily:
$field->display('name');
Alternatively, you may pass a Closure
:
$field->display(static function (Model $model): string {
return strtoupper($model->name);
});
Aggregates
On index
views, often it makes more sense to display an aggregated value of the relation. For example, when a model has a BelongsToMany
relation with a bunch of attached record, it's not really optimal to show all the related model's formatted value in the table row, but the number of the attached values.
You may customize this aggregation logic using the aggregate
function. The first parameter is the aggregate function, while the second one is the aggregated column.
$field->aggregate('count', 'id');
// or
$field->aggregate('sum', 'tax');
The available aggregate functions: count
, min
, max
, sum
and avg
.
Grouping
You may also group options for a better UI. This feature wraps the <option>
elements into an <optgroup>
.
$field->groupOptionsBy('category_id');
Alternatively, you may pass a Closure
:
$field->groupOptionsBy(static function (Model $model): string {
return $model->category->name;
});
Subresources
When relation fields need more robust management you might convert it as a subresource. That means, instead of rendering a <select>
on the resource form, the relation field gets its own CRUD interfaces as it would be a resource.
$field->asSubResource();
Subresources appear on the parent resource models's show route.
Fields
When using a field as subresource, you may define the related model's fields:
$field->withFields(static function (Request $request): array {
return [
Textarea::make('Body'),
Boolean::make('Approved'),
];
});
Filters
When using a field as subresource, you may define the filters for its index
view:
use App\Root\Filters\CategoryFilter;
$field->withFilters(static function (Request $request): array {
return [
new CategoryFilter,
];
});
Actions
When using a field as subresource, you may define the filters for its index
view:
use App\Root\Actions\SendOrderNotification;
$field->withActions(static function (Request $request): array {
return [
new SendOrderNotification,
];
});
The BelongsTo
field is typically a handler for an existing Illuminate\Database\Eloquent\Relations\BelongsTo
relation:
$field = BelongsTo::make(__('Author'), 'author');
The BelongsTo
field cannot be a subresource.
The BelongsToMany
field is typically a handler for a Illuminate\Database\Eloquent\Relations\BelongsToMany
relation:
$field = BelongsToMany::make(__('Teams'), 'teams');
When using as subresource, you may define editable pivot fields. Use the withPivotFields
instead of the withFields
:
Only existing models can be attached, creating relatable models from the subresource is not supported.
$field->withPivotFields(static function (Request $request): array {
return [
Select::make('Role', 'role')->options([
'admin' => 'Admin',
'member' => 'Member',
]),
];
});
Sometimes you may want to attach the same model multiple times (maybe with different pivot values). To do so, call the allowDuplicateRelations
method on the field:
$field->allowDuplicateRelations();
When duplications are allowed, the relatable model query includes the already attached models as well, otherwise they are excluded from the query.
The HasMany
field is typically a handler for a Illuminate\Database\Eloquent\Relations\HasMany
relation:
$field = HasMany::make(__('Comments'), 'comments');
When using as subresource, you may define the related model's fields:
$field->withFields(static function (Request $request): array {
return [
Textarea::make('Body'),
Boolean::make('Approved'),
];
});
The HasOne
field is typically a handler for a Illuminate\Database\Eloquent\Relations\HasOne
relation:
$field = HasMany::make(__('Comments'), 'comments');
The MorphMany
field is typically a handler for a Illuminate\Database\Eloquent\Relations\MorphMany
relation:
The MorphOne
field is typically a handler for a Illuminate\Database\Eloquent\Relations\MorphOne
relation:
The MorphToMany
field is typically a handler for a Illuminate\Database\Eloquent\Relations\MorphToMany
relation: