---
title: "Column mapping — Imports for Laravel | Roundly"
description: "Mapping suggestions from headers, labels, aliases and fuzzy matches, map() by header or column, and saved mappings per owner that apply themselves."
url: https://roundly-consulting.com/open-source/docs/imports-for-laravel/column-mapping
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)

# Column mapping

After upload, every column gets a suggested field. A complete suggestion — every required field, nothing fuzzy — is applied at upload (status mapped); otherwise the session waits for you (needs\_mapping).

## How columns are suggested

Headers are normalised: ASCII-folded, lower-cased, a trailing \* / (required) / (povinné) dropped, everything else non-alphanumeric turned into \_ (Inventárne č. \* → inventarne\_c). Each column then gets the best of:

| Match | Confidence | Source |
| --- | --- | --- |
| a saved mapping for this exact header row | 1.00 | `saved` |
| the field name | 1.00 | `name` |
| a label in headers.locales, the current or the fallback locale | 0.98 | `label` |
| an alias | 0.95 | `alias` |
| a fuzzy match ≥ headers.fuzzy\_threshold — flagged, never applied without review | ≥ 0.85 | `fuzzy` |

Greedy, each field once, ties to the leftmost column.

```php
$suggestion = $session->suggestMapping();
$suggestion->columns;            // list<SuggestedColumn>: index, header, field, confidence, source; isFuzzy()
$suggestion->unmappedRequired;   // field names
$suggestion->ignoredColumns;     // column indexes
$suggestion->savedMapping;       // the saved mapping's name, or null
$suggestion->isComplete();

$session->map($suggestion->toMapping());   // apply it as it is
```

## Map by header or column

```php
use RoundlyConsulting\Imports\DataTransferObjects\ColumnMapping;

$session->map(['Inventárne č.' => 'asset_tag', 3 => 'name']);   // by header text or 0-based column index (3 = column D)
$session->map(['Notes' => null]);                               // leave a column unmapped
$session->map(ColumnMapping::fromArray([2 => 'name']));         // always by 0-based column index
$session->saveMapping('ERP export');
```

- Integer keys are 0-based column indexes — unless a header reads that number (a header 2024 becomes an integer key in PHP), in which case it maps by its text.
- A number that is a header and the index of a different column is refused rather than guessed — map it explicitly with ColumnMapping, which is always by index.
- Refused with InvalidMappingException (422, errors keyed columns.<column> / fields.<field>): an unknown field or column, a field mapped twice, a duplicated header named by text (use its number), a required field left unmapped.
- Changing the mapping drops the preview and bumps preview\_version; a commit against an older version is refused.
- Once a commit holds the session — even one started a moment earlier from another tab or worker — a mapping change is refused with ImportStateException, so it never rewrites a running commit’s plan.

## Saved mappings

Name a mapping once and every later file with the same headers maps itself — even with the columns in a different order. Save it from a session or from header text:

```php
$mappings = Imports::for($team)->by($user)->mappings('assets');

$mappings->all();                                     // list<SavedMapping>
$mappings->find('ERP export');                        // ?SavedMapping
$mappings->save('ERP export', ['Inventárne č.' => 'asset_tag', 'Názov' => 'name']);
$mappings->delete('ERP export');                      // false when there was none
```

- Both $session->saveMapping($name) (the session’s headers and mapping, plus its delimiter, sheet and header row) and mappings($importer)->save() run SaveMappingAction, so both store the same shape, fire MappingSaved and are recorded alike by the fake. Saving under an existing name replaces it.
- The action checks, in order: saved mappings are on; a row importer; authorize(Upload); a 1–60 character name, at least one column, every field known and mapped once; then the per-importer and size caps. Refusals throw InvalidSavedMappingException or TooManySavedMappingsException (both 422).
- Stored per owner through options-for-laravel (the imports.mappings option, one row per owner), keyed by normalised header, and auto-applied to an identical header row — never when normalised headers collide.
- Header signatures are locale-independent — always transliterated under en — so a mapping saved by a Slovak user is recognised for a German one.
- Up to mappings.max\_per\_importer (20) per importer and mappings.max\_bytes in all. Writes are serialised with a cache lock — set mappings.lock\_store to a shared store.

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