Fields

Basics

Fields play a vital role in the MoonShine admin panel.
They are used in FormBuilder to build forms, in TableBuilder to create tables, as well as in forming a filter for ModelResource. They can be used in your custom pages and even outside the admin panel.
Fields in MoonShine are not tied to the model (except Slug field, ModelRelationFields, Json in asRelation mode), therefore, the range of their applications is limited only by your imagination.

For convenience, fields have fluent interface.

Make

To create an instance of a field, use static method make().

Text::make(Closure|string|null $label = null, ?string $column = null, ?Closure $formatted = null)
  • $label - label, field title,
  • $column - a field in the database (for example name) or a relation (for example countries),
  • $formatted - closure for formatting the field value during preview (everywhere except the form).

If you do not specify $column, then the field in the database will be determined automatically based on $label.

Value formatting

//...
 
public function fields(): array
{
return [
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.

Label

If you need to change the Label, you can use the setLabel() method

setLabel(Closure|string $label)
//...
 
public function fields(): array
{
return [
Slug::make('Slug')
->setLabel(
fn(Field $field) => $field->getData()?->exists
? 'Slug (do not change)'
: 'Slug'
)
];
}
 
//...

To translate Label you need to pass the translation key as the name and add translatable() method

translatable(string $key = '')
//...
 
public function fields(): array
{
return [
Text::make('Title')->translatable('ui')
];
}
 
//...

or

//...
 
public function fields(): array
{
return [
Text::make('ui.Title')->translatable()
];
}
 
//...

Attributes

Basic html attributes such as required, disabled and readonly, must be specified by the appropriate methods on the field.

disabled(Closure|bool|null $condition = null)
hidden(Closure|bool|null $condition = null)
required(Closure|bool|null $condition = null)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->disabled()
->hidden()
->readonly()
];
}
 
//...

The ability to specify any other attributes using the custom Attributes() method.

customAttributes(array $attributes)
//...
 
public function fields(): array
{
return [
Password::make('Title')
->customAttributes(['autocomplete' => 'off'])
];
}
 
//...

Method customWrapperAttributes() allows you to add attributes for a wrapper field.

customWrapperAttributes(array $attributes)
//...
 
public function fields(): array
{
return [
Password::make('Title')
->customWrapperAttributes(['class' => 'mt-8'])
];
}
 
//...

Alpine.js

Methods that allow you to conveniently interact with Alpine.js

xData(null|array|string $data = null)

Everything in Alpine starts with the x-data directive. The xData method defines an HTML fragment as an Alpine component and provides reactive data to reference that component.

Block::make([])->xData(['title' = 'Hello world']) // title is a reactive variable inside
xDataMethod(string $method, ...$parameters)

x-data indicating the component and its parameters

Block::make([])->xDataMethod('some-component', 'var', ['foo' => 'bar'])
xModel(?string $column = null)

x-model binding a field to a reactive variable

Block::make([
Text::make('Title')->xModel()
])->xData(['title' = 'Hello world'])
 
// or
 
Block::make([
Text::make('Name')->xModel('title')
])->xData(['title' = 'Hello world'])
xIf(
string|Closure $variable,
?string $operator = null,
?string $value = null,
bool $wrapper = true
)

x-if hides a field, removing it from the DOM

Block::make([
Select::make('Type')->native()->options([1 => 1, 2 => 2])->xModel(),
Text::make('Title')->xModel()->xIf('type', 1)
])->xData(['title' = 'Hello world', 'type' => 1])
 
// or
 
Block::make([
Select::make('Type')->options([1 => 1, 2 => 2])->xModel(),
Text::make('Title')->xModel()->xIf(fn() => 'type==2||type.value==2')
])->xData(['title' = 'Hello world', 'type' => 1])
 
// if you need to hide a field without a container
 
Block::make([
Select::make('Type')->native()->options([1 => 1, 2 => 2])->xModel(),
Text::make('Title')->xModel()->xIf('type', '=', 2, wrapper: false)
])->xData(['title' = 'Hello world', 'type' => 1])
xShow(
string|Closure $variable,
?string $operator = null,
?string $value = null,
bool $wrapper = true
)

x-show is the same as x-if, but does not remove the element from the DOM, it only hides it

xDisplay(string $value, bool $html = true)

x-html output value

Block::make([
Select::make('Type')
->native()
->options([
1 => 'Paid',
2 => 'Free',
])
->xModel(),
 
Number::make('Cost', 'price')
->xModel()
->xIf('type', '1'),
 
Number::make('Rate', 'rate')
->xModel()
->xIf('type', '1')
->setValue(90),
 
LineBreak::make(),
 
Div::make()
->xShow('type', '1')
->xDisplay('"Result:" + (price * rate)')
,
])->xData([
'price' => 0,
'rate' => 90,
'type' => '2',
]),

Clue

You can add a hint with a description to a field by calling method hint()

hint(string $hint)
//...
 
public function fields(): array
{
return [
Number::make('Rating')
->hint('From 0 to 5')
->min(0)
->max(5)
->stars()
];
}
 
//...

hint hint_dark

You can add a link to the field (for example, with instructions) link().

link(
string|Closure $link,
string|Closure $name = '',
?string $icon = null,
bool $withoutIcon = false,
bool $blank = false
)
//...
 
public function fields(): array
{
return [
Text::make('Link')
->link('https://cutcode.dev', 'CutCode', blank: true)
];
}
 
//...

link link_dark

Nullable

If you need to keep NULL for a field by default, you must use method nullable().

nullable(Closure|bool|null $condition = null)
//...
 
public function fields(): array
{
return [
Password::make('Title')
->nullable()
];
}
 
//...

Sorting

To be able to sort a field on the main resource page, you need to add method sortable().

sortable(Closure|string|null $callback = null)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->sortable()
];
}
 
//...

Method sortable() can take the name of a field in the database or a closure as a parameter.

//...
 
public function fields(): array
{
return [
BelongsTo::make('Author')->sortable('author_id'),
 
Text::make('Title')->sortable(function (Builder $query, string $column, string $direction) {
$query->orderBy($column, $direction);
})
];
}
 
//...

Badge

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

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

Available colors:

  • primary

  • secondary

  • success

  • warning

  • error

  • info

  • purple

  • pink

  • blue

  • green

  • yellow

  • red

  • gray

//...
 
public function fields(): array
{
return [
Text::make('Title')
->badge(fn($status, Field $field) => 'green')
];
}
 
//...

Horizontal display

The horizontal() method allows you to display the title and field horizontally.

horizontal()
//...
 
public function fields(): array
{
return [
Text::make('Title')
->horizontal(),
];
}
 
//...

horizontal horizontal_dark

Display

In a model resource, fields are displayed on the list page (main page) and on the create/edit/view pages.

To exclude the display of a field on any page, you can use the appropriate methods hideOnIndex(), hideOnForm(), hideOnDetail() or reverse methods showOnIndex(), showOnForm(), showOnDetail().

To exclude only from the edit or add page - hideOnCreate(), hideOnUpdate(), as well as reverse showOnCreate(), showOnUpdate

In order to exclude a field on all pages, you can use the hideOnAll() method.

hideOnIndex(Closure|bool|null $condition = null)
showOnIndex(Closure|bool|null $condition = null)
hideOnForm(Closure|bool|null $condition = null)
showOnForm(Closure|bool|null $condition = null)
 
hideOnCreate(Closure|bool|null $condition = null)
showOnCreate(Closure|bool|null $condition = null)
 
hideOnUpdate(Closure|bool|null $condition = null)
showOnUpdate(Closure|bool|null $condition = null)
hideOnDetail(Closure|bool|null $condition = null)
showOnDetail(Closure|bool|null $condition = null)
hideOnAll(Closure|bool|null $condition = null)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->hideOnIndex()
->hideOnForm(),
 
Switcher::make('Active')
->hideOnAll()
->showOnIndex(static fn() => true)
];
}
 
//...

If you just need to specify which fields to display on pages or change the order of display, then you can use a convenient method field overrides.

Dynamic display

It may be necessary to display a field only if the value of another field in the form has a certain value (for example: display the phone only if there is a checkmark that there is a phone).
Method showWhen() is used for this purpose.

showWhen(
string $column,
mixed $operator = null,
mixed $value = null
)

Available operators:

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

If operator is not specified, = will be used

//...
 
public function fields(): array
{
return [
Checkbox::make('Has phone', 'has_phone'),
Phone::make('Phone')
->showWhen('has_phone','=', 1)
];
}
 
//...

If the operator is in or not in, then in $value you need to pass an array, and the values as a string.

//...
 
public function fields(): array
{
return [
Select::make('List', 'list')->multiple()->options([
'value 1' => 'Option Label 1',
'value 2' => 'Option Label 2',
'value 3' => 'Option Label 3',
]),
 
Text::make('Name')
->showWhen('list', 'not in', ['value 1', 'value 3']),
 
Textarea::make('Content')
->showWhen('list', 'in', ['value 2', 'value 3'])
];
}
 
//...

In the showWhen() method for the Json and BelongsToMany fields You can access nested values via .:

->showWhen('data.content.active', '=', 1)

Changing the display

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

customView(string $customView)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->customView('fields.my-custom-input')
];
}
 
//...

Method changePreview() allows you to override the view for the preview (everywhere except the form).

changePreview(Closure $closure)
//...
 
public function fields(): array
{
return [
Text::make('Thumbnail')
->changePreview(function ($value, Field $field) {
return view('moonshine::ui.image', [
'value' => Storage::url($value)
]);
})
];
}
 
//...

The forcePreview() method will indicate that the field should always be in preview mode

Text::make('Label')->forcePreview()

The requestValueResolver() method allows you to override the logic for getting a value from Request

requestValueResolver(Closure $closure)
//...
 
public function fields(): array
{
return [
Text::make('Thumbnail')
->requestValueResolver(function (string $nameDot, mixed $default, Field $field) {
return request($nameDot, $default);
})
];
}
 
//...

beforeRender() and afterRender() methods allows you to display some information before and after the field, respectively.

beforeRender(Closure $closure)
afterRender(Closure $closure)
//...
 
public function fields(): array
{
return [
Image::make('Thumbnail')
->beforeRender(function (Field $field) {
return $field->preview();
})
];
}
 
//...

Methods by condition

Method when() implements a fluent interface and executes callback when the first argument passed to the method is true.

when($value = null, callable $callback = null)
//...
 
public function fields(): array
{
return [
Text::make('Slug')
->when(fn() => true, fn(Field $field) => $field->locked())
];
}
 
//...

An instance of the field will be passed to the callback function.

The second callback can be passed to method when(), it will be executed, when the first argument passed to the method is false.

when($value = null, callable $callback = null, callable $default = null)
//...
 
public function fields(): array
{
return [
Text::make('Slug')
->when(
auth('moonshine')->user()->moonshine_user_role_id === 1,
fn(Field $field) => $field->locked(),
fn(Field $field) => $field->readonly()
)
];
}
 
//...

Method unless() is the inverse of method when() and will execute the first callback, when the first argument is false, otherwise the second callback will be executed if it is passed to the method.

unless($value = null, callable $callback = null, callable $default = null)
//...
 
public function fields(): array
{
return [
Text::make('Slug')
->unless(
auth('moonshine')->user()->moonshine_user_role_id === 1,
fn(Field $field) => $field->readonly()->hideOnCreate(),
fn(Field $field) => $field->locked()
)
];
}
 
//...

Filling

Fields can be filled with values using the fill() method.

fill(mixed $value, mixed $casted = null)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->fill('Some title')
];
}
 
//...

The changeFill() method allows you to change the logic of filling a field with values.

changeFill(mixed $value, mixed $casted = null)
//...
 
public function fields(): array
{
return [
Text::make('Categories')
->changeFill(
fn(Article $data, Field $field) => $data->categories->implode('title', ',')
)
];
}
 
//...

Relationship fields do not support the changeFill method

Apply

Each field has an apply() method, which transforms the data taking into account the request and resolve methods. For example, it transforms model data for saving in a database or generates a query for filtering.

It is possible to override the actions when executing the apply() method, To do this, you need to use the onApply() method, which accepts a closure.

onApply(Closure $onApply)
//...
 
public function fields(): array
{
return [
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;
})
];
}
 
//...

If the field is used to build a filter, then a Query Builder will be passed to the closure.

use Illuminate\Contracts\Database\Eloquent\Builder;
 
//...
 
public function filters(): array
{
return [
Switcher::make('Active')
->onApply(fn(Builder $query, $value, Field $field) => $query->where('active', $value))
];
}
 
//...

If you do not want the field to perform any actions, then you can use the canApply() method.

canApply(Closure|bool|null $condition = null)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->canApply()
];
}
 
//...

Events

Sometimes you may need to override resolve methods that are executed before and after apply(), to do this, you must use appropriate methods.

onBeforeApply(Closure $onBeforeApply)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->onBeforeApply(function(Model $item, $value, Field $field) {
//
return $item;
})
];
}
onAfterApply(Closure $onAfterApply)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->onAfterApply(function(Model $item, $value, Field $field) {
//
return $item;
})
];
}
onAfterDestroy(Closure $onAfterDestroy)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->onAfterDestroy(function(Model $item, $value, Field $field) {
//
return $item;
})
];
}

Assets

For the field, it is possible to load additional CSS styles and JS scripts using the addAssets() method.

addAssets(array $assets)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->addAssets(['custom.css', 'custom.js'])
];
}

Wrapper

When displayed on forms, fields use a special wrapper for titles, tooltips, links, etc. Sometimes a situation may arise when you want to display a field without additional elements.
Method withoutWrapper() allows you to disable the creation of wrapper.

withoutWrapper(mixed $condition = null)
//...
 
public function fields(): array
{
return [
Text::make('Title')
->withoutWrapper()
];
}

without_wrapper without_wrapper_dark

Reactive

The reactive() method allows you to change fields reactively.

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

Callback

The Callback function in the reactive() method accepts parameters which you can use to build your logic.

function(Fields $fields, ?string $value, Field $field, array $values)
  • $fields - reactive fields
  • $value - the value of the field that triggers reactivity
  • $field - field that initiates reactivity
  • $values - values of reactive fields.

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

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()
])

This example implements the formation of a slug field based on the header.
The Slug will be generated as you enter text.

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

To change the state of the field that initiates 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()
);
);
})

onChange methods

Using the onChangeMethod() and onChangeUrl() methods You can add logic when changing field values.

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

onChangeUrl()

The onChangeUrl() method allows you to send a request asynchronously when a field changes.

onChangeUrl(
Closure $url,
string $method = 'PUT',
array $events = [],
?string $selector = null,
?string $callback = null,
)
  • $url - request url
  • $method - asynchronous request method
  • $events - events to be called after a successful request,
  • $selector - selector of the element whose content will change
  • $callback - js callback function after receiving a response.
//...
 
public function fields(): array
{
return [
Switcher::make('Active')
->onChangeUrl(fn() => '/endpoint')
];
}

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

//...
 
public function fields(): array
{
return [
Switcher::make('Active')
->onChangeUrl(fn() => '/endpoint', selector: '#my-selector')
];
}

onChangeMethod()

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

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

Example of sorting the CardsBuilder component in the section Recipes

Methods for values

Get value from source

The fromRaw() method allows you to add a closure to get the final value from the original.
This closure is used when importing data.

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

Get raw value

The modifyRawValue() method allows you to add a closure to obtain the raw value.
This closure is used when exporting data.

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

Scheme field's work

field_scheme]