Fields and values
Definition\Field is fluent and immutable — every call returns a copy, so a shared field definition can be refined per importer safely:
use App\Enums\AssetStatus;
use RoundlyConsulting\Imports\Definition\Field;
Field::text('asset_tag')->label('app.asset.tag')->aliases('tag', 'inventory no')->example('AST-0042');
Field::integer('quantity')->min(1);
Field::decimal('price', 2);
Field::boolean('insured')->trueValues('ja')->falseValues('nein');
Field::date('purchased_on')->formats('m/d/Y'); // US dates are opt-in
Field::datetime('seen_at');
Field::email('email')->required();
Field::choice('status')->options(AssetStatus::class)->default('active');
Field::reference('vendor')->to(Vendor::class)->by('vat_id', 'name')->caseInsensitive();Types and coercion
| Type | Coercion |
|---|---|
Field::text($name) | Numbers keep their digits (5, not 5.0; scientific notation expanded up to an exponent of ±400, beyond that kept as written); dates become Y-m-d; booleans TRUE/FALSE; at most the importer’s limits() cell length (default limits.cell_length). |
Field::integer($name) | A number cell or text with thousands separators; 5.0 is fine, 5.5 and overflow are errors; scientific notation with an exponent beyond ±400 is invalid_integer. |
Field::decimal($name, $scale = 2) | A numeric string, never a float: text in the session locale (1 299,50 in sk), the last separator wins when both appear, rounded half-up in string arithmetic (0.30000000000000004 → 0.30); scientific notation with an exponent beyond ±400 (1e999999999) is invalid_number. |
Field::boolean($name) | English and Slovak always — 1 true yes y x ✓ áno ano a pravda / 0 false no n nie nepravda — plus trueValues() and falseValues(). |
Field::date($name) · Field::datetime($name) | XLSX dates (wall-clock time, DST-change days included); Excel serials 1 … 2958465 in the workbook’s 1900 or 1904 system (CSV is 1900); text by values.date_formats / datetime_formats, then formats(), round-trip checked — 31.02.2026 is refused, 01/02/2026 is never guessed. The file’s clock times are read in the session timezone (values.timezone); a DateTime then reaches the importer as that instant in app.timezone (2026-01-15 09:30 read in Europe/Bratislava → 08:30 UTC), which is what Eloquent stores. A Date stays the calendar day, unconverted. |
Field::email($name) | Lower-cased and checked with email:rfc. |
Field::choice($name) | The value or a label in any configured locale, case-insensitive → the canonical value. The cell is matched as typed first (01 and 1.50 are codes of their own), then as a number (1.0 → option 1); an exponent beyond ±400 is invalid_choice. |
Field::reference($name) | Looked up in batches inside the owner → the resolved model, as $row->reference('vendor'). |
Field settings
Field::text('name')
->label(['en' => 'Name', 'sk' => 'Názov']) // a translation key, text or locale map
->help(['en' => 'As on the invoice', 'sk' => 'Ako na faktúre'])
->aliases(['sk' => ['nazov zariadenia']]) // plain or per locale
->required()
->max(120)
->rules('regex:/^[A-Z0-9 -]+$/i') // any Laravel rule — not exists:/unique:
->normalizeUsing(fn ($value, ?Cell $cell) => is_string($value) ? Str::squish($value) : $value);
Field::choice('status')->options(['active' => 'Active', 'retired' => ['en' => 'Retired', 'sk' => 'Vyradený']]);
Field::choice('gender')->options([1 => 'Male', 2 => 'Female']); // numeric codes: stored values 1 and 2
Field::text('internal_note')->excludeFromTemplate();
Field::reference('vendor')
->to(Vendor::class)
->by('vat_id', 'name') // lookup columns, tried in order
->ownedBy(Ownership::global()) // vendors are shared — default: the importer's rule
->storeAs('vendor_id'); // attributes() and changes() carry the key here
Field::reference('maker')->to(Vendor::class)->by('name')->default('Lenovo'); // unmapped: every row gets Lenovo| Method | Meaning |
|---|---|
label() · help() | A translation key, text or a locale map — headers, template tips and messages. |
required() · nullable() | nullable is the default. |
default($value) · example($value) | The value for a blank cell; the template’s example value. |
aliases(...) | Other headers that map to the field — plain or ['sk' => [...]]. |
rules(...) | Laravel validation rules. exists: and unique: (strings or Rule objects) are refused at registration — they bypass the owner scope. Use reference() and naturalKey(). |
normalizeUsing(fn ($value, ?Cell $cell) => …) | Your clean-up, after trimming and before type coercion. |
max() · min() | Length for text, value for numbers. |
excludeFromTemplate() | Leave the column out of templates. |
formats(...) | Date / DateTime: extra createFromFormat formats, tried after values.date_formats. |
trueValues(...) · falseValues(...) | Boolean: your own words. |
options(...) | Choice: value => label (labels may be locale maps), a list, or a backed enum (labels from enums-for-laravel’s label()). Numeric codes keep their values ([1 => 'Male', 2 => 'Female'] → 1 and 2); only a true list (keys 0, 1, 2, …) is read as values without labels, so a map starting at 0 must be written as a list or with string keys. |
to() · by() · ownedBy() · caseInsensitive() · storeAs() | Reference: the model, lookup columns tried in order, its own Ownership (default Ownership::inherit() — the importer’s rule), case-insensitive matching, and the column attributes() and changes() put the referenced key in (without it the reference is left out of both). An unmapped reference with a default() is looked up from its default for every row; no match is unknown_reference in the preview. |
labelIn() · helpIn() · aliasesIn() · optionLabel() · exampleValue() | Read helpers for a UI or a custom template; every setting is also a public read-only property (name, type, required, options, target, lookup, storeAs, …). |
The value pipeline
Every cell runs through the same steps, in order:
- 1. Trim — also non-breaking spaces, U+202F and zero-width characters; a blank cell becomes null.
- 2. normalizeUsing().
- 3. Type coercion — a failure is a cell error, never an exception.
- 4. default() when the value is null.
- 5. Validation: required, the type’s own rules (max: / min: — length for text, value for numbers; email:rfc) and rules(), with field labels as attribute names and messages in the session locale.
- 6. Reference lookup — batched, inside the owner.
References
Field::reference() links a row to another record — a vendor by VAT number, then by name. Lookups run in batches and inside the owner: by default the reference inherits the importer’s Ownership, ->ownedBy() gives it its own (Ownership::global() for shared records). The resolved model is $row->reference('vendor'); storeAs('vendor_id') makes attributes() and changes() carry its key. No match is unknown_reference, several are ambiguous_reference. A reference with a default() that no column is mapped to is looked up from its default for every row (default('Lenovo') → the Lenovo vendor); a default that finds nothing fails each row with unknown_reference in the preview.
Cell error codes
| Code | Meaning |
|---|---|
invalid_integer · integer_overflow · invalid_number · invalid_boolean | The value could not be coerced to the field’s type — also scientific notation with an exponent beyond ±400. |
invalid_date · invalid_datetime | No configured format matched, or the date does not exist. |
invalid_choice · too_long · invalid_value | Not an allowed value; over the importer’s cell length (limits.cell_length by default); not usable as a value. |
spreadsheet_error | An XLSX error cell such as #N/A. |
extra_values | A CSV value beyond the header’s last column. |
unknown_reference · ambiguous_reference | No record, or several, for a reference inside the owner. |
ambiguous_match · conflicting_match | Two records for one natural key; a later key candidate finds a different record. |
matches_deleted_record | Only a soft-deleted record holds the key — restore it or change the identifier. |
required, max, email, … | Laravel’s rule names, with field labels as attribute names and messages in the session locale. |
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.