Fields

Basic Methods

Description

All fields inherit the base class Field, which provides basic methods for working with fields.

Create Field

To create an instance of a field, the static method make() is used.

Text::make(Closure|string|null $label = null, ?string $column = null, ?Closure $formatted = null)
  • $label - the label or title of the field.
  • $column - the relationship between the database column and the name attribute of the input field (e.g.: description > <input name="description">). If this field is a relationship, the name of the relationship is used (e.g.: countries).
  • $formatted - a closure for formatting the field's value in preview mode (for BelongsTo and BelongsToMany, formats values for selection).

HTML tags can be added in $label, they will not be escaped.

If $column is not specified, the database field will be automatically determined based on $label (only for English).

Example of a closure $formatted for formatting a value.

Text::make(
'Name',
'first_name',
fn($item) => $item->first_name . ' ' . $item->last_name
)

Fields that do not support formatted: Json, File, Range, RangeSlider, DateRange, Select, Enum, HasOne, HasMany.

Field Display

Label

If you need to change the Label after creating an instance of the field, you can use the setLabel() method.

setLabel(Closure|string $label)
Slug::make('Slug')
->setLabel(
fn(Field $field) => $field->getData()?->exists
? 'Slug (do not change)'
: 'Slug'
)

To translate Label, you must pass a translation key as the name and add the translatable() method.

translatable(string $key = '')
Text::make('ui.Title')->translatable()

or

Text::make('Title')->translatable('ui')

or

Text::make(fn() => __('Title'))

insideLabel()

To wrap a field in a <label> tag, you can use the insideLabel() method.

Text::make('Name')
->insideLabel(),

beforeLabel()

To display the Label after the input field, you can use the beforeLabel() method.

Text::make('Name')
->beforeLabel(),

Hint

A hint with a description can be created using the hint() method.

hint(string $hint)
Number::make('Rating')
->hint('From 0 to 5')
->min(0)
->max(5)
->stars()

Link

You can add a link to the field (e.g., with instructions) using link().

link(
string|Closure $link,
string|Closure $name = '',
?string $icon = null,
bool $withoutIcon = false,
bool $blank = false
)
Text::make('Link')
->link('https://cutcode.dev', 'CutCode', blank: true)

Badge

To display the field in preview mode as a badge, you need to use the badge() method.

badge(string|Color|Closure|null $color = null)

Available colors:

primary secondary success warning error info

purple pink blue green yellow red gray

Text::make('Title')
->badge(Color::PRIMARY)

or

Text::make('Title')
->badge(fn($status, Field $field) => 'green')

Horizontal Display

The horizontal() method allows displaying the label and the field horizontally.

horizontal()
Text::make('Title')
->horizontal(),

Wrapper

Fields when displayed in forms use a special wrapper for headers, hints, links, etc. Sometimes there may arise a situation when it is necessary to display a field without additional elements. The withoutWrapper() method allows disabling the creation of a wrapper.

withoutWrapper(mixed $condition = null)
Text::make('Title')
->withoutWrapper()

Sorting

To allow sorting the field in tables (on the main page), you must add the sortable() method.

sortable(Closure|string|null $callback = null)
Text::make('Title')->sortable()

The sortable() method can accept a database field name or a closure as a parameter.

BelongsTo::make('Author')->sortable('author_id'),
 
Text::make('Title')->sortable(function (Builder $query, string $column, string $direction) {
$query->orderBy($column, $direction);
})

View Modes

You can read more about view modes in the section Basics > Change View Mode.

Default Mode

To ensure that the field always works in "Default" mode (render as "input" field) regardless of context, use the defaultMode() method.

Text::make('Title')->defaultMode()

Preview Mode

To ensure that the field always works in "Preview" mode regardless of context, use the previewMode() method.

Text::make('Title')->previewMode()

RawMode

To ensure that the field always works in "RawMode" (render as the original state), use the rawMode() method.

Text::make('Title')->rawMode()

Attributes

Basic HTML attributes such as required, disabled, and readonly should be specified for the field through their respective methods.

Required

required(Closure|bool|null $condition = null)
Text::make('Title')->required()

Disabled

disabled(Closure|bool|null $condition = null)
Text::make('Title')->disabled()

Readonly

readonly(Closure|bool|null $condition = null)
Text::make('Title')->readonly()

Other Attributes

To specify any other attributes, the customAttributes() method is used.

Fields are components, read more about attributes in the section Component Attributes

customAttributes(array $attributes, bool $override = false)
  • $attributes - an array of attributes
  • $override - to add attributes to the field, use merge. If the attribute you want to add to the field already exists, it will not be added. Setting $override = true allows changing this behavior and overwriting the already added attribute.
Password::make('Title')
->customAttributes(['autocomplete' => 'off'])

Attributes for Wrapper Field

The customWrapperAttributes() method allows adding attributes for the wrapper field.

customWrapperAttributes(array $attributes)
Password::make('Title')
->customWrapperAttributes(['class' => 'mt-8'])

Modifying the "name" Attribute

wrapName

To add a wrapper for the value of the name attribute, the wrapName method is used.

Text::make('Name')->wrapName('options')

As a result, the name attribute will look like <input name="options[name]>. This is especially useful for setting up filters.

virtualName

Sometimes it is necessary to store two values in one input field. For example, under a display condition one of the fields may become invisible but still exist in the DOM and be sent with the request.

File::make('image') // this is displayed in showWhen under one condition
File::make('image') // this is displayed in showWhen under another condition

To change the name attribute of these fields, the virtualName method is used.

File::make('image')->virtualColumn('image_1')
File::make('image')->virtualColumn('image_2')

Then, for example in the onApply method, we can handle these fields as we see fit.

Modifying Field Value

Default Value

To specify a default value, the default() method is used.

default(mixed $default)
Text::make('Name')
->default('Default value')

or

Enum::make('Status')
->attach(ColorEnum::class)
->default(ColorEnum::from('B')->value)

Nullable

If you need to keep NULL as the default value for the field, then the nullable() method should be used.

nullable(Closure|bool|null $condition = null)
Password::make('Title')
->nullable()

Changing Display

When you need to change the view using the fluent interface, you can use the customView() method.

customView(string $view, array $data = [])
Text::make('Title')->customView('fields.my-custom-input')

The changePreview() method allows overriding the view for preview (everywhere except the form).

Text::make('Thumbnail')
->changePreview(function (?string $value, Text $field) {
return Thumbnails::make($value);
})

Hook Before Render

If you need to access a field just before rendering, you can use the onBeforeRender() method.

/**
* @param Closure(static $ctx): void $onBeforeRender
*/
public function onBeforeRender(Closure $onBeforeRender): static
Text::make('Thumbnail')->onBeforeRender(function(Text $ctx) {
//
})

Getting Value from Request

The requestValueResolver() method allows overriding the logic for obtaining the value from the Request.

/**
* @param Closure(string|int|null $index, mixed $default, static $ctx): mixed $resolver
*/
requestValueResolver(Closure $resolver)

Relationship fields do not support the requestValueResolver method.

Before and After Rendering

The beforeRender() and afterRender() methods allow displaying some information before and after the field, respectively.

beforeRender(Closure $closure)
afterRender(Closure $closure)
Text::make('Title')
->beforeRender(function(Field $field) {
return $field->preview();
})

Conditional Methods

Components can be displayed conditionally using the canSee() method.

Text::make('Name')
->canSee(function (Text $field) {
return $field->toValue() !== 'hide';
})

or for relationship fields:

BelongsTo::make('Item', 'item', resource: ItemResource::class)
->canSee(function (Comment $comment, BelongsTo $field) {
// your condition
})
,

The when() method implements a fluent interface and will execute the callback when the first argument passed to the method is true.

when($value = null, ?callable $callback = null, ?callable $default = null)
Text::make('Slug')
->when(fn() => true, fn(Field $field) => $field->locked())

The unless() method is the opposite of the when() method.

unless($value = null, ?callable $callback = null, ?callable $default = null)

Apply

Each field has an apply() method that transforms the data. To override the default apply of a field, you can use the onApply() method. Read more about the lifecycle of field application in the section Basics > Field Application Process.

/**
* @param Closure(mixed, mixed, FieldContract): mixed $onApply
*/
onApply(Closure $onApply)
Text::make('Thumbnail by link', 'thumbnail')
->onApply(function(Model $item, $value, Field $field) {
$path = 'thumbnail.jpg';
 
if ($value) {
$item->thumbnail = Storage::put($path, file_get_contents($value));
}
 
return $item;
})

To perform actions before "apply", you can use the onBeforeApply() method.

/**
* @param Closure(mixed, mixed, FieldContract): static $onBeforeApply
*/
function onBeforeApply(Closure $onBeforeApply)

To perform actions after "apply", you can use the onAfterApply() method.

/**
* @param Closure(mixed, mixed, FieldContract): static $onBeforeApply
*/
function onAfterApply(Closure $onBeforeApply)

Global Definition of Apply Logic

If you want to globally change the apply logic for a certain field, you can create an apply class and bind it to the necessary field.

First, create the apply class:

php artisan moonshine:apply FileModelApply
/**
* @implements ApplyContract<File>
*/
final class FileModelApply implements ApplyContract
{
/**
* @param File $field
*/
public function apply(FieldContract $field): Closure
{
return function (mixed $item) use ($field): mixed {
$requestValue = $field->getRequestValue();
 
$newValue = // ..
 
return data_set($item, $field->getColumn(), $newValue);
};
}
}

Then register it for the field:

use Illuminate\Support\ServiceProvider;
use MoonShine\Contracts\Core\DependencyInjection\CoreContract;
use MoonShine\Laravel\DependencyInjection\MoonShine;
use MoonShine\Laravel\DependencyInjection\MoonShineConfigurator;
use MoonShine\Laravel\DependencyInjection\ConfiguratorContract;
use MoonShine\Contracts\Core\DependencyInjection\AppliesRegisterContract;
use MoonShine\UI\Applies\AppliesRegister;
use App\MoonShine\Applies\FileModelApply;
use MoonShine\UI\Fields\File;
use MoonShine\Laravel\Resources\ModelResource;
 
class MoonShineServiceProvider extends ServiceProvider
{
/**
* @param MoonShine $core
* @param MoonShineConfigurator $config
* @param AppliesRegister $applies
*
*/
public function boot(
CoreContract $core,
ConfiguratorContract $config,
AppliesRegisterContract $applies,
): void
{
$applies
// resource group, default ModelResource
->for(ModelResource::class)
// type fields or filters
->fields()
->add(File::class, FileModelApply::class);
}
}

Fill

Fields can be filled with values using the fill() method. You can read more details about the filling process in the section Basics > Change Fill.

fill(mixed $value = null, ?DataWrapperContract $casted = null, int $index = 0)
Text::make('Title')
->fill('Some title')

Changing the Field Filling Logic

To change the filling logic of a field, you can use the changeFill() method.

Select::make('Images')->options([
'/images/1.png' => 'Picture 1',
'/images/2.png' => 'Picture 2',
])
->multiple()
->changeFill(
fn(Article $article, Select $ctx) => $article->images
->map(fn($value) => "https://cutcode.dev$value")
->toArray()
);

In this example, https://cutcode.dev will be added to the image paths.

Actions After Filling the Field

To apply logic to an already filled field, you can use the afterFill() method.

A similar logic method when triggers when creating a field instance, when it is not yet filled. The afterFill method works with an already filled field.

Select::make('Links')->options([
'/images/1.png' => 'Picture 1',
'/images/2.png' => 'Picture 2',
])
->multiple()
->afterFill(
function(Select $ctx) {
if(collect($ctx->toValue())->every(fn($value) => str_contains($value, 'cutcode.dev'))) {
return $ctx->customWrapperAttributes(['class' => 'full-url']);
}
 
return $ctx;
}
)

onChange Methods

Using the onChangeMethod() and onChangeUrl() methods, you can add logic when field values change.

The methods onChangeUrl() or onChangeMethod() are present on all fields except for HasOne and HasMany relationship fields.

onChangeUrl()

onChangeUrl(
Closure $url,
HttpMethod $method = HttpMethod::GET,
array $events = [],
?string $selector = null,
?AsyncCallback $callback = null,
)
  • $url - the request url,
  • $method - the method for the asynchronous request,
  • $events - triggers AlpineJS events after a successful request,
  • $selector - the selector of the element whose content will change,
  • $callback - a js callback function after receiving a response.
Switcher::make('Active')
->onChangeUrl(fn() => '/endpoint')

If you need to replace an area with HTML after a successful request, you can return HTML content or JSON with the key html in the response.

Switcher::make('Active')
->onChangeUrl(fn() => '/endpoint', selector: '#my-selector')

onChangeMethod()

The onChangeMethod() method allows asynchronously calling a resource or page method when the field changes without the need to create additional controllers.

onChangeMethod(
string $method,
array|Closure $params = [],
?string $message = null,
?string $selector = null,
array $events = [],
?AsyncCallback $callback = null,
?PageContract $page = null,
?ResourceContract $resource = null,
)
  • $method - the name of the method,
  • $params - parameters for the request,
  • $message - messages,
  • $selector - the selector of the element whose content will change,
  • $events - triggers AlpineJS events after a successful request,
  • $callback - a js callback function after receiving a response,
  • $page - the page containing the method,
  • $resource - the resource containing the method.
Switcher::make('Active')
->onChangeMethod('someMethod')
public function someMethod(MoonShineRequest $request): void
{
// Logic
}

Changing Field Render

To completely change the render of a field, you can use the changeRender() method.

changeRender(Closure $callback)

In this example, the Select field transforms into text:

Select::make('Links')->options([
'/images/1.png' => 'Picture 1',
'/images/2.png' => 'Picture 2',
])
->multiple()
->changeRender(
fn(?array $values, Select $ctx) => Text::make($ctx->getLabel())->fill(implode(',', $values))
)

Methods for Values

Obtaining Value from Raw

The fromRaw() method allows adding a closure to obtain the final value from the raw one.

/**
* @param Closure(mixed $raw, static): mixed $callback
* @return $this
*/
fromRaw(Closure $callback)
use App\Enums\StatusEnum;
use MoonShine\UI\Fields\Enum;
 
Enum::make('Status')
->attach(StatusEnum::class)
->fromRaw(fn(string $raw, Enum $ctx) => StatusEnum::tryFrom($raw))

Obtaining Raw Value

The modifyRawValue() method allows adding a closure to obtain a raw value.

/**
* @param Closure(mixed $raw, static): mixed $callback
* @return $this
*/
modifyRawValue(Closure $callback)
use App\Enums\StatusEnum;
use MoonShine\UI\Fields\Enum;
 
Enum::make('Status')
->attach(StatusEnum::class)
->modifyRawValue(fn(StatusEnum $raw, Enum $ctx) => $raw->value))

Editing in Preview Mode

Editing in preview mode is available for Text, Number, Checkbox, Select, Date fields.

For editing fields in preview mode, such as in a table or any other IterableComponent, there are the following methods.

updateOnPreview

The updateOnPreview() method allows editing a field in preview mode. After making changes (onChange event), the value of the field will be saved for the specific item.

public function updateOnPreview(
?Closure $url = null,
?ResourceContract $resource = null,
mixed $condition = null,
array $events = [],
)
  • $url - (optional) request url,
  • $resource - (optional) resource containing updateOnPreview,
  • $condition - (optional) condition for setting the field to updateOnPreview mode,
  • $events - (optional) triggers AlpineJS events after a successful request.

Parameters are not mandatory but should be provided if the field is outside a resource or if you want to specify a completely custom endpoint (then the resource is not needed)

Text::make('Name')->updateOnPreview()

withUpdateRow

withUpdateRow() works similarly to updateOnPreview(), but can completely update the row in the table without reloading the page.

public function withUpdateRow(string $component)
  • $component - the name of the component that contains this row.
Text::make('Name')->withUpdateRow('index-table-post-resource')

withUpdateRow() can use all parameters from updateOnPreview(), for example, to change the request url; they need to be called together.

Text::make('Name')->updateOnPreview(url: '/my/url')->withUpdateRow()

updateInPopover

The updateInPopover() method works similarly to the withUpdateRow() method, but now all values for editing appear in a separate window.

public function updateInPopover(string $component)
  • $component - the name of the component that contains this row.
Text::make('Name')->updateInPopover('index-table-post-resource')

The methods updateOnPreview, withUpdateRow, and updateInPopover create the necessary endpoints and pass them to the setUpdateOnPreviewUrl() method, which works with onChangeUrl()

Assets

To add assets to the field, you can use the addAssets() method.

Text::make('Name')
->addAssets([
new Css(Vite::asset('resources/css/text-field.css'))
]),

If you are implementing your custom field, you can declare the asset set in it in two ways.

  1. Through the assets() method:
/**
* @return list<AssetElementContract>
*/
protected function assets(): array
{
return [
Js::make('/js/custom.js'),
Css::make('/css/styles.css')
];
}
  1. Through the booted() method:
protected function booted(): void
{
parent::booted();
 
$this->getAssetManager()
->add(Css::make('/css/app.css'))
->append(Js::make('/js/app.js'));
}

Macroable Trait

All fields have access to the Illuminate\Support\Traits\Macroable trait with the mixin and macro methods. You can use this trait to extend the functionality of fields by adding new features without the need for inheritance.

Field::macro('myMethod', fn() => /*implementation*/)
 
Text::make()->myMethod()

or

Field::mixin(new MyNewMethods())

Reactivity

The reactive() method allows reactively changing fields.

reactive(
?Closure $callback = null,
bool $lazy = false,
int $debounce = 0,
int $throttle = 0,
)
  • $callback - callback function,
  • $lazy - delayed function call,
  • $debounce - time between function calls (ms.),
  • $throttle - function call interval (ms.).

Fields that support reactivity: Text, Number, Checkbox, Select, Date, and their descendants.

FormBuilder::make()
->name('my-form')
->fields([
Text::make('Title')
->reactive(function(Fields $fields, ?string $value): Fields {
return tap($fields, static fn ($fields) => $fields
->findByColumn('slug')
?->setValue(str($value ?? '')->slug()->value())
);
}),
 
Text::make('Slug')
->reactive()
])

In this example, the slug field is created based on the title. The slug will be generated during the input process.

A reactive field can change the state of other fields but does not change its own state!

To change the state of the field initiating reactivity, it is convenient to use the parameters of the callback function.

Select::make('Category', 'category_id')
->reactive(function(Fields $fields, ?string $value, Field $field, array $values): Fields {
$field->setValue($value);
 
return tap($fields, static fn ($fields) =>
$fields
->findByColumn('article_id')
?->options(
Article::where('category_id', $value)
->get()
->pluck('title', 'id')
->toArray()
);
);
})

Dynamic Display

To change the display of fields depending on the values of other fields in real-time, without reloading the page and making server requests, the showWhen and showWhenDate methods are used.

showWhen Method

The showWhen method allows setting a display condition for a field based on the value of another field.

public function showWhen(
string $column,
mixed $operator = null,
mixed $value = null
): static
  • $column - the name of the field on which the display depends,
  • $operator - comparison operator (optional),
  • $value - value for comparison.
Text::make('Name')
->showWhen('category_id', 1)

In this example, the field "Name" will only be displayed if the value of the field "category_id" is equal to 1.

If only two parameters are passed to the showWhen function, the '=' operator is used by default.

Text::make('Name')
->showWhen('category_id', 'in', [1, 2, 3])

In this example, the field "Name" will only be displayed if the value of the field "category_id" is equal to 1, 2, or 3.

showWhenDate Method

The showWhenDate method allows setting a display condition for a field based on the value of a date-type field. The logic for working with dates has been separated into a specific method due to the specifics of converting and comparing date and datetime types on both the backend and frontend.

public function showWhenDate(
string $column,
mixed $operator = null,
mixed $value = null
): static
  • $column - the name of the date field on which the display depends,
  • $operator - comparison operator (optional),
  • $value - date value for comparison.
Text::make('Content')
->showWhenDate('created_at', '>', '2024-09-15 10:00')

In this example, the field "Content" will only be displayed if the value of the field "created_at" is greater than '2024-09-15 10:00'.

If only two parameters are passed to the showWhenDate function, the '=' operator is used by default.

You can use any date format that can be recognized by the strtotime() function.

Nested Fields

The showWhen and showWhenDate methods support working with nested fields, such as working with the Json field. Point notation is used to access nested fields.

Text::make('Parts')
->showWhen('attributes.1.size', '!=', 2)

In this example, the field "Parts" will only be displayed if the value of the nested field "size" in the second element of the array "attributes" is not equal to 2.

showWhen also works with nested Json fields:

Json::make('Attributes', 'attributes')->fields([
Text::make('Size'),
Text::make('Parts')
->showWhen('category_id', 3)
,
Json::make('Settings', 'settings')->fields([
Text::make('Width')
->showWhen('category_id', 3)
,
Text::make('Height'),
])
]),

In this example, the entire column Parts inside attributes and the entire column Width inside attributes.[n].settings will only be displayed if the value of the field category_id is equal to 3.

Multiple Conditions

The showWhen and showWhenDate methods can be called multiple times for the same field, allowing the specification of several display conditions.

BelongsTo::make('Category', 'category', resource: CategoryResource::class)
->showWhenDate('created_at', '>', '2024-08-05 10:00')
->showWhenDate('created_at', '<', '2024-08-05 19:00')

In this example, the field "Category" will only be displayed if the value of the field "created_at" is within the range between '2024-08-05 10:00' and '2024-08-05 19:00'.

When using multiple conditions, they are combined logically with "AND". The field will be displayed only if all specified conditions are fulfilled.

Supported Operators

  • =
  • !=
  • >
  • <
  • >=
  • <=
  • in
  • not in

The in operator checks if the value is in the array. The not in operator checks if the value is not in the array.

Custom Field

You can create a custom field with your view and logic and use it in the MoonShine administration panel. To do this, use the command:

php artisan moonshine:field