---
title: "Fields and values — Imports for Laravel | Roundly"
description: "The nine field types and their coercion, labels, aliases, examples, rules and normalisers, references to other records and every cell error code."
url: https://roundly-consulting.com/open-source/docs/imports-for-laravel/fields
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Imports for Laravel](https://roundly-consulting.com/open-source/docs/imports-for-laravel.md)

# 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:

```php
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

```php
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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/imports-for-laravel.md)
