Fields
Fields describe the input and display shape of a Resource form. They are used by Core form validation and by UI/API adapters where configured.
Defining Fields
use SeviaFlow\Core\Resources\Fields\EmailField;
use SeviaFlow\Core\Resources\Fields\TextField;
public static function form(Form $form): Form
{
return $form->fields([
TextField::make('name')->label('Name')->required(),
EmailField::make('email')->label('Email')->requiredOnCreate(),
]);
}
Current Field Types
The 0.1 catalog contains 27 field types. RichText uses one bounded, sanitized HTML contract; Repeater uses one bounded, one-level JSON object schema. The six simple value fields below use the same Core validation and UI adapter contract as the original types.
| Field | Purpose | Field-specific methods |
|---|---|---|
BelongsToField | Select an Eloquent belongs-to record. | relationship, optionsLimit, query, searchableOptions, inlineCreate, inlineCreateLabel, dependsOn |
BelongsToManyField | Select multiple scoped Eloquent related records in the main form. | relationship, resource, query, searchableOptions, optionsLimit, minSelected, maxSelected |
CheckboxGroupField | Multiple allowed values with an empty/min/max contract. | options, minSelected, maxSelected |
ColorField | A labeled six-digit HEX color. | HEX validation and picker/text UI. |
DateField | Date value. | Default date validation. |
DateTimeField | Date and time value. | Default datetime validation. |
EmailField | Email string. | Default email validation. |
FileField | Uploaded file path/value. | disk, directory, maxSize, acceptedFileTypes |
ImageField | Uploaded image path/value. | Image validation through the field type. |
JsonField | Bounded JSON with an explicit root type. | rootType, maxBytes, maxDepth |
KeyValueField | Flat string-to-string JSON object. | maxEntries, keyLength, valueLength |
MoneyField | Numeric money value. | currency |
MultiSelectField | Multiple selected options with the same option sources as Select. | searchable, dependsOn, disabledOptions, options, minSelected, maxSelected |
NumberField | Numeric value. | min, max |
PasswordField | Password input. | dehydrateOnlyWhenFilled |
PhoneField | Phone string. | Default string validation. |
RadioGroupField | One allowed value with accessible grouped options. | options, optionDescriptions |
RepeaterField | One-level JSON array of objects with stable UI row identity. | fields, minItems, maxItems, itemLabel, maxBytes |
RichTextField | Bounded sanitized HTML for paragraphs, headings, emphasis, lists and safe links. | maxBytes; storage format sanitized-html. |
SelectField | Single option selection with grouped, enum or dependent options. | options, searchable, dependsOn, disabledOptions |
SlugField | Slug string derived from another field. | from |
TextField | Single-line text. | Default string validation. |
TextareaField | Multi-line text. | rows |
TagsField | Trimmed, deduplicated string tags. | minTags, maxTags, tagLength |
TimeField | Time value. | Default time validation. |
ToggleField | Boolean value. | Default boolean validation. |
UrlField | URL string. | Default URL validation. |
Select Options
SelectField and MultiSelectField accept a flat value-to-label array, grouped arrays, a PHP enum class, or a callback receiving FormState. Backed enums keep their backing values; unit enums use case names. Option IDs must be unique across groups. MultiSelect is searchable by default; Select search is opt-in.
SelectField::make('country')
->options(['Europe' => ['PL' => 'Poland', 'NO' => 'Norway']])
->searchable();
SelectField::make('status')->options(CustomerStatus::class);
SelectField::make('city')
->dependsOn('country')
->options(static fn (FormState $state): array =>
$state->value('country') === 'PL'
? ['byd' => 'Bydgoszcz'] : ['osl' => 'Oslo']);
MultiSelectField::make('channels')
->options(['email' => 'Email', 'phone' => 'Phone'])
->disabledOptions(['phone']);
Declare each dependency with dependsOn. The UI clears invalid dependent choices when those values change; server validation independently evaluates the current submitted FormState and rejects unknown or disabled choices. Static sources expose an enum in the transport schema; dynamic sources are validated at submission time.
Many Records in a Form
BelongsToManyField::make('categories')
->relationship('categories', 'name')
->resource(CategoryResource::class)
->query(static fn (Builder $query): Builder => $query->where('active', true))
->searchableOptions(['name'])
->optionsLimit(50)
->maxSelected(8);
The model must define an Eloquent BelongsToMany relationship and its pivot table. This field uses a list of string IDs, not a JSON model attribute. Resource scope and the query callback restrict options and validate writes. Selected allowed records remain visible beyond the search result limit. Parent save and relation sync use one transaction; omitted values retain links, and an empty list clears editable links. Existing links outside the editable scope are preserved. Set selection limits for your use case; the default server limit is 1000 IDs. Pivot attributes are edited through relation managers.
Simple Field Examples
Each example is a complete field definition that can be placed in a Resource form. Collection fields are persisted as arrays; key/value and JSON input are parsed as JSON, never evaluated as code.
RadioGroup
RadioGroupField::make('service_tier')
->options(['standard' => 'Standard', 'priority' => 'Priority'])
->optionDescriptions(['priority' => 'Faster response target.'])
->required();
CheckboxGroup
CheckboxGroupField::make('channels')
->options(['email' => 'Email', 'phone' => 'Phone'])
->minSelected(1)
->maxSelected(2);
Tags
TagsField::make('tags')
->maxTags(8)
->tagLength(40);
KeyValue
KeyValueField::make('metadata')
->maxEntries(12)
->keyLength(60)
->valueLength(500);
Json
JsonField::make('automation_payload')
->rootType('object')
->maxBytes(16384)
->maxDepth(6);
Color
ColorField::make('brand_color')
->label('Brand color');
RichText
RichTextField::make('rich_description')
->maxBytes(32768);
Repeater JSON
RepeaterField::make('contact_methods')
->itemLabel('label')
->fields([
TextField::make('label')->required(),
SelectField::make('channel')->options(['email' => 'Email', 'phone' => 'Phone']),
NumberField::make('priority')->min(1)->max(5),
MoneyField::make('monthly_cost')->currency('EUR'),
ToggleField::make('enabled'),
EmailField::make('email'),
DateField::make('available_from'),
TextareaField::make('notes')->maxLength(500),
])
->maxItems(8)
->maxBytes(8192);
Repeater stores a JSON array of objects with only declared scalar fields: Text, Number, Money, Select, Toggle, Textarea, Date, DateTime, Time, Email, Url, Phone, RadioGroup and Color. Unknown keys, nested values, excessive rows and oversized payloads are rejected. Dependent Select options are evaluated against each row. UI row keys are transient and never enter the model or API. This is not a HasMany adapter, invoice engine or arbitrary nested repeater.
CSV export/import supports Repeater as one JSON array string using the same schema and byte limit. Other unsupported shapes are rejected rather than silently cast. CSV import accepts the same JSON representation for CheckboxGroup, Tags, KeyValue, Json and Repeater; invalid JSON or values outside the declared contract are rejected by server validation. RadioGroup and Color use their scalar text representation.
RichText storage and API use sanitized HTML. The allowlist is p, h2, h3, strong, em, ul, ol, li, a and br; links allow only HTTP, HTTPS, mailto and safe relative targets. CSV export/import uses this same HTML string and enforces the byte limit; no media, embed, table or Markdown conversion is promised.
Common Field Capabilities
| Capability | Method | What it controls |
|---|---|---|
| Identity | make('email'), name() | Connect the field to the model attribute named email. The same name is used for validation and form values. |
| Display copy | label(...), help(...), placeholder(...) | Set the visible label, explanatory text, and input hint for the person filling in the form. |
| Initial value | default(...) | Provide a value when the field has no submitted value on create. An omitted update field keeps its stored value. |
| Value contract | nullable(), required() | Declare whether null is allowed and whether a value is required in the form's normal contexts. |
| Interaction | disabled(), readonly() | Prevent editing in the UI. Use a disabled field when it should be unavailable; use readonly when the value should remain visible but unchanged by the user. |
| Context-specific state | requiredOn(...), disabledOn(...), readonlyOn(...) | Apply the state only on named Resource contexts such as create, edit, or show. Convenience methods include requiredOnCreate(), disabledOnEdit(), and readonlyOnShow(). |
| Visibility | visibleOn(...), hiddenOn(...) | Choose the Resource contexts where a field appears. Convenience methods include visibleOnCreate(), visibleOnEdit(), and hiddenOnShow(). |
| Validation | rules(...), rulesOnCreate(...), rulesOnEdit(...), rulesOnUpdate(...) | Add Laravel validation rules globally or for a specific operation. withDefaultValidation() and withoutDefaultValidation() control validation inferred from the field type. |
| String validation helpers | minLength(...), maxLength(...), length(...), regex(...) | Set minimum, maximum, or exact text length, or require a value to match a regular expression. |
| Layout | columnSpan(...) | Choose how much of the form grid this field occupies. |
Context-specific state and visibility affect the UI surface. API exposure and API payload validation still follow the Resource's explicit API contract. Field-specific behavior, such as option sources or a repeater schema, is listed with each field type above.
Field Contract and Adapters
Every field exposes a stable type(), a transport schema through getSchema(), and deterministic normalizeValue(), prepareForValidation() and prepareForImport() hooks. Core owns these value concerns; it does not import Blade, Livewire or HTTP.
The UI package maps the field type to explicit edit, show and cell renderers through SeviaFlow\Ui\Fields\FieldRendererRegistry. A missing adapter is an integration error, not a silent text input fallback.
final class TrackingCodeField extends Field
{
protected static string $type = 'tracking-code';
protected function defaultApiSchema(): array
{
return ['type' => 'string', 'pattern' => '^[A-Z]+$'];
}
}
$registry->register(
'tracking-code',
'app.fields.tracking-code.edit',
'app.fields.tracking-code.show',
'app.fields.tracking-code.cell',
);
Register the renderer from the host application or an extension service provider; the main SeviaFlow Blade form remains unchanged. For a headless resource, declare schemas explicitly with ApiResource::schema([...]), especially for UUIDs, invoice numbers, money and collections. Existing headless resources retain only the conservative legacy primary/foreign-key default; new semantic types should use an explicit schema.
Validation Rules
TextField::make('name')
->required()
->minLength(2)
->maxLength(120);
EmailField::make('email')
->rulesOnCreate('unique:customers,email')
->rulesOnUpdate('email');
Get Started