SeviaFlow Get Started

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.

FieldPurposeField-specific methods
BelongsToFieldSelect an Eloquent belongs-to record.relationship, optionsLimit, query, searchableOptions, inlineCreate, inlineCreateLabel, dependsOn
BelongsToManyFieldSelect multiple scoped Eloquent related records in the main form.relationship, resource, query, searchableOptions, optionsLimit, minSelected, maxSelected
CheckboxGroupFieldMultiple allowed values with an empty/min/max contract.options, minSelected, maxSelected
ColorFieldA labeled six-digit HEX color.HEX validation and picker/text UI.
DateFieldDate value.Default date validation.
DateTimeFieldDate and time value.Default datetime validation.
EmailFieldEmail string.Default email validation.
FileFieldUploaded file path/value.disk, directory, maxSize, acceptedFileTypes
ImageFieldUploaded image path/value.Image validation through the field type.
JsonFieldBounded JSON with an explicit root type.rootType, maxBytes, maxDepth
KeyValueFieldFlat string-to-string JSON object.maxEntries, keyLength, valueLength
MoneyFieldNumeric money value.currency
MultiSelectFieldMultiple selected options with the same option sources as Select.searchable, dependsOn, disabledOptions, options, minSelected, maxSelected
NumberFieldNumeric value.min, max
PasswordFieldPassword input.dehydrateOnlyWhenFilled
PhoneFieldPhone string.Default string validation.
RadioGroupFieldOne allowed value with accessible grouped options.options, optionDescriptions
RepeaterFieldOne-level JSON array of objects with stable UI row identity.fields, minItems, maxItems, itemLabel, maxBytes
RichTextFieldBounded sanitized HTML for paragraphs, headings, emphasis, lists and safe links.maxBytes; storage format sanitized-html.
SelectFieldSingle option selection with grouped, enum or dependent options.options, searchable, dependsOn, disabledOptions
SlugFieldSlug string derived from another field.from
TextFieldSingle-line text.Default string validation.
TextareaFieldMulti-line text.rows
TagsFieldTrimmed, deduplicated string tags.minTags, maxTags, tagLength
TimeFieldTime value.Default time validation.
ToggleFieldBoolean value.Default boolean validation.
UrlFieldURL 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

CapabilityMethodWhat it controls
Identitymake('email'), name()Connect the field to the model attribute named email. The same name is used for validation and form values.
Display copylabel(...), help(...), placeholder(...)Set the visible label, explanatory text, and input hint for the person filling in the form.
Initial valuedefault(...)Provide a value when the field has no submitted value on create. An omitted update field keeps its stored value.
Value contractnullable(), required()Declare whether null is allowed and whether a value is required in the form's normal contexts.
Interactiondisabled(), 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 staterequiredOn(...), disabledOn(...), readonlyOn(...)Apply the state only on named Resource contexts such as create, edit, or show. Convenience methods include requiredOnCreate(), disabledOnEdit(), and readonlyOnShow().
VisibilityvisibleOn(...), hiddenOn(...)Choose the Resource contexts where a field appears. Convenience methods include visibleOnCreate(), visibleOnEdit(), and hiddenOnShow().
Validationrules(...), 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 helpersminLength(...), maxLength(...), length(...), regex(...)Set minimum, maximum, or exact text length, or require a value to match a regular expression.
LayoutcolumnSpan(...)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');