---
title: "Configuration — Imports for Laravel | Roundly"
description: "Every key of config/imports.php with its env variable and default — key types, storage, limits, reading, preview, commit, undo, queue, templates, API."
url: https://roundly-consulting.com/open-source/docs/imports-for-laravel/configuration
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)

# Configuration

The package works with zero configuration. Publish config/imports.php only to override:

```bash
php artisan vendor:publish --tag="imports-config"
```

Every value is read strictly: a value of the wrong type or outside its range throws InvalidImportsConfigurationException naming the key, and a blank env value (KEY=) takes the default.

- Durations are ISO-8601 (P7D, PT30M) and must be positive.
- Switches accept any boolean, including on and off.
- Integers must sit in the range each row gives; a value outside it throws rather than being clamped.
- Class keys (owner scope, resources, owner resolver) must name a class of the right kind; anything else throws, naming the key.

## The config file

```php
use RoundlyConsulting\Imports\Http\Resources\ImportActorResource;
use RoundlyConsulting\Imports\Http\Resources\ImportOwnerResource;
use RoundlyConsulting\Imports\Http\Resources\ImportRecordResource;
use RoundlyConsulting\Imports\Models\ImportBatch;
use RoundlyConsulting\Imports\Models\ImportedRecord;
use RoundlyConsulting\Imports\Models\ImportRowResult;
use RoundlyConsulting\Imports\Models\ImportSession;
use RoundlyConsulting\Imports\Scoping\ExplicitOwnerScope;

return [
    'importers' => [],

    'key_type' => env('IMPORTS_KEY_TYPE', 'bigint'),
    'record_key_type' => env('IMPORTS_RECORD_KEY_TYPE'),
    'primary_key_type' => env('IMPORTS_PRIMARY_KEY_TYPE', 'bigint'),

    'models' => [
        'session' => ImportSession::class,
        'batch' => ImportBatch::class,
        'row_result' => ImportRowResult::class,
        'record' => ImportedRecord::class,
    ],

    'tables' => [
        'sessions' => 'import_sessions',
        'batches' => 'import_batches',
        'row_results' => 'import_row_results',
        'records' => 'imported_records',
    ],

    'owner_scope' => ExplicitOwnerScope::class,

    'storage' => [
        'disk' => env('IMPORTS_DISK', 'local'),        // never a public disk
        'path' => 'imports',
        'retention' => env('IMPORTS_RETENTION', 'P30D'),
        'tmp_path' => env('IMPORTS_TMP_PATH'),         // blank = sys_get_temp_dir()/roundly-imports
        'tmp_ttl' => 'PT6H',
    ],

    'history' => [
        'retention' => env('IMPORTS_HISTORY_RETENTION'),   // blank = keep forever
    ],

    'limits' => [
        'file_size' => env('IMPORTS_MAX_FILE_SIZE', 10240),     // KB
        'rows' => env('IMPORTS_MAX_ROWS', 10000),               // hard ceiling 100,000
        'columns' => env('IMPORTS_MAX_COLUMNS', 100),           // hard ceiling 16,384
        'cell_length' => 32767,
    ],

    'csv' => [
        'delimiters' => [',', ';'],
        'sniff_bytes' => 65536,
        'unescape_formula_prefix' => true,
    ],

    'xlsx' => [
        'max_entries' => 1000,
        'max_part_size' => 268435456,
        'max_inflated' => 536870912,
        'max_blank_rows' => 10000,
        'shared_strings_memory' => 2097152,
        'allow_macro_enabled' => false,
    ],

    'headers' => [
        'locales' => ['en', 'sk'],
        'fuzzy' => true,
        'fuzzy_threshold' => 0.85,
    ],

    'values' => [
        'date_formats' => ['Y-m-d', 'd.m.Y', 'j.n.Y', 'j. n. Y'],
        'datetime_formats' => ['Y-m-d H:i:s', 'Y-m-d H:i', 'd.m.Y H:i', 'j.n.Y H:i'],
        'timezone' => null,   // blank = the application timezone
    ],

    'preview' => [
        'sync_threshold' => env('IMPORTS_SYNC_THRESHOLD', 500),
        'window' => 500,
        'progress_every' => 250,
    ],

    'commit' => [
        'chunk_size' => 250,
        'max_chunks' => 100,
        'tries' => 3,
        'backoff' => [10, 60, 300],
        'timeout' => 600,
    ],

    'undo' => [
        'window' => env('IMPORTS_UNDO_WINDOW', 'P7D'),
    ],

    'sessions' => [
        'expire_after' => 'P2D',
        'stale_after' => 'PT30M',
        'abandon_after' => 'PT6H',
    ],

    'queue' => [
        'enabled' => env('IMPORTS_QUEUE_ENABLED', true),
        'connection' => env('IMPORTS_QUEUE_CONNECTION'),
        'queue' => env('IMPORTS_QUEUE_NAME'),
    ],

    'files' => [
        'max_files' => 500,
        'max_uncompressed' => 1073741824,
        'max_ratio' => 100,
        'ratio_min_size' => 1048576,
        'max_file_size' => 51200,       // KB
        'max_archive_size' => 1048576,  // KB
        'ignore' => ['__MACOSX/*', '*.DS_Store', 'Thumbs.db', 'desktop.ini'],
    ],

    'templates' => [
        'default_format' => 'xlsx',
        'example_row' => true,
        'hint_row' => ['csv' => true, 'xlsx' => false],
        'data_validation' => true,
        'csv_delimiter' => ['default' => ',', 'sk' => ';'],
        'csv_bom' => true,
    ],

    'mappings' => [
        'enabled' => true,
        'max_per_importer' => 20,
        'max_bytes' => 60000,
        'lock_store' => env('IMPORTS_LOCK_STORE'),
    ],

    'resources' => [
        'owner' => ImportOwnerResource::class,
        'actor' => ImportActorResource::class,
        'record' => ImportRecordResource::class,
    ],

    'routes' => [
        'enabled' => env('IMPORTS_ROUTES') ?: 'off',
        'prefix' => 'api/imports',
        'middleware' => ['api', 'auth'],
        'upload_limit' => env('IMPORTS_UPLOAD_LIMIT', 10),
        'owner_resolver' => null,
    ],
];
```

## Importers, key types, models and tables

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `importers` | — | \[\] | Classes extending Importer or FileImporter. More at runtime with Imports::register(). |
| `key_type` | `IMPORTS_KEY_TYPE` | bigint | bigint, uuid or ulid — the owner and actor morphs. Must equal options.key\_type: saved mappings are owner-scoped options. |
| `record_key_type` | `IMPORTS_RECORD_KEY_TYPE` | blank = key\_type | The imported-record morph — every importer model’s key type. |
| `primary_key_type` | `IMPORTS_PRIMARY_KEY_TYPE` | bigint | The package tables’ own ids. Read by the migrations — set it before migrating. |
| `models.session / batch / row_result / record` | — | ImportSession / ImportBatch / ImportRowResult / ImportedRecord | Swap a model by extending the packaged class and naming the subclass here. |
| `tables.sessions / batches / row_results / records` | — | import\_sessions / import\_batches / import\_row\_results / imported\_records | ^\[a-z\_\]\[a-z0-9\_\]{0,29}$ — at most 30 characters, so derived index names stay under MySQL’s 64. |
| `owner_scope` | — | ExplicitOwnerScope::class | A Contracts\\OwnerScope class — keeps every lookup and write inside the owner (see Owners and permissions). |

## Storage and retention

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `storage.disk` | `IMPORTS_DISK` | local | Where uploads are stored. Never a public disk — imports:doctor reports one as an error. |
| `storage.path` | — | imports | Files go under <path>/<owner digest>/<session id>/. |
| `storage.retention` | `IMPORTS_RETENTION` | P30D | Source files, working copies and row results are deleted by imports:prune after this. |
| `storage.tmp_path` | `IMPORTS_TMP_PATH` | blank = sys\_get\_temp\_dir()/roundly-imports | Temp files — a non-local disk is copied here when a file needs random access. |
| `storage.tmp_ttl` | — | PT6H | Orphaned temp directories older than this are removed by imports:sweep. |
| `history.retention` | `IMPORTS_HISTORY_RETENTION` | blank = keep forever | Sessions, batches and ledger rows past it and past their undo window are deleted by imports:prune. |

## Limits

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `limits.file_size` | `IMPORTS_MAX_FILE_SIZE` | 10240 | KB, 1 … 2,097,152 — checked before the file is read. |
| `limits.rows` | `IMPORTS_MAX_ROWS` | 10000 | Data rows, 1 … 100,000. |
| `limits.columns` | `IMPORTS_MAX_COLUMNS` | 100 | 1 … 16,384; non-empty cells only, so formatted empty columns do not count. |
| `limits.cell_length` | — | 32767 | Characters per cell, 1 … 1,048,576. An importer overrides it with limits()->withCellLength(). |

## Reading CSV and XLSX

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `csv.delimiters` | — | \[',', ';'\] | One-character sniffing candidates. |
| `csv.sniff_bytes` | — | 65536 | 1,024 … 1,048,576 bytes read to sniff the delimiter. |
| `csv.unescape_formula_prefix` | — | true | Strip the ' the package’s own CSV writer puts before formula-like cells. |
| `xlsx.max_entries` | — | 1000 | 1 … 100,000 ZIP entries in a workbook. |
| `xlsx.max_part_size` | — | 268435456 | ≥ 1,024 bytes per part, counted while inflating (256 MB). |
| `xlsx.max_inflated` | — | 536870912 | ≥ 1,024 bytes across one read (512 MB). |
| `xlsx.max_blank_rows` | — | 10000 | ≥ 1 — the longest run of empty rows allowed between two rows with data. Data below a longer gap rejects the file (data\_after\_blank\_rows); a formatted blank tail with nothing below is ignored. |
| `xlsx.shared_strings_memory` | — | 2097152 | ≥ 1,024 bytes of shared strings kept in memory, then spooled to disk (2 MB). |
| `xlsx.allow_macro_enabled` | — | false | Accept .xlsm workbooks — macros never run. |

## Headers and values

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `headers.locales` | — | \['en', 'sk'\] | Locales whose labels match headers, plus the current and fallback locale. |
| `headers.fuzzy` | — | true | Suggest close spellings — flagged, never applied without review. |
| `headers.fuzzy_threshold` | — | 0.85 | 0.5 … 1.0. |
| `values.date_formats` | — | \['Y-m-d', 'd.m.Y', 'j.n.Y', 'j. n. Y'\] | PHP createFromFormat formats tried for date text; a field adds its own with formats(). |
| `values.datetime_formats` | — | \['Y-m-d H:i:s', 'Y-m-d H:i', 'd.m.Y H:i', 'j.n.Y H:i'\] | The same for date-times. |
| `values.timezone` | — | blank = app.timezone | A timezone identifier — the clock the file’s dates and times are read in. DateTime values are then converted to app.timezone before an importer sees them; Date values are not. |

## Preview, commit, undo and sessions

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `preview.sync_threshold` | `IMPORTS_SYNC_THRESHOLD` | 500 | 0 … 100,000 — up to this many data rows validate in the request, more on the queue. Also the inline rollback size. |
| `preview.window` | — | 500 | 1 … 10,000 rows per validation window. |
| `preview.progress_every` | — | 250 | 1 … 100,000 rows between ImportPreviewProgressed events. |
| `commit.chunk_size` | — | 250 | 1 … 10,000 rows per chunk. |
| `commit.max_chunks` | — | 100 | 1 … 1,000 — the chunk size grows to fit. |
| `commit.tries` | — | 3 | 1 … 100 attempts per chunk job. |
| `commit.backoff` | — | \[10, 60, 300\] | Non-negative seconds between attempts. |
| `commit.timeout` | — | 600 | 1 … 86,400 seconds — keep the queue’s retry\_after above it (the doctor checks). |
| `undo.window` | `IMPORTS_UNDO_WINDOW` | P7D | How long a finished import can be undone. |
| `sessions.expire_after` | — | P2D | Uncommitted sessions expire (imports:sweep). |
| `sessions.stale_after` | — | PT30M | No progress for this long and no live job → failed. |
| `sessions.abandon_after` | — | PT6H | Silent for this long → failed regardless. |

## Queue

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `queue.enabled` | `IMPORTS_QUEUE_ENABLED` | true | Off: previews validate in the request and batches run on the sync connection. |
| `queue.connection` | `IMPORTS_QUEUE_CONNECTION` | blank = default | The connection of previews, commits and rollbacks. |
| `queue.queue` | `IMPORTS_QUEUE_NAME` | blank = default | The queue name. |

## File batches

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `files.max_files` | — | 500 | 1 … 100,000 files or ZIP entries per batch. |
| `files.max_uncompressed` | — | 1073741824 | ≥ 1,024 bytes — the declared total of an archive (1 GB). |
| `files.max_ratio` | — | 100 | 1 … 100,000 — the compression ratio allowed. |
| `files.ratio_min_size` | — | 1048576 | ≥ 0 bytes — the ratio applies above this size (1 MB). |
| `files.max_file_size` | — | 51200 | KB — the per-file default when FileRules sets none. |
| `files.max_archive_size` | — | 1048576 | KB per archive. |
| `files.ignore` | — | \['\_\_MACOSX/\*', '\*.DS\_Store', 'Thumbs.db', 'desktop.ini'\] | Entries skipped. |

## Templates

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `templates.default_format` | — | xlsx | xlsx or csv. |
| `templates.example_row` | — | true | Write an example row. |
| `templates.hint_row.csv / .xlsx` | — | true / false | Write a hint row (required, allowed values, formats). |
| `templates.data_validation` | — | true | XLSX dropdowns for choice and yes/no columns. |
| `templates.csv_delimiter` | — | \['default' => ',', 'sk' => ';'\] | Per locale; default is required. |
| `templates.csv_bom` | — | true | Start CSV templates with a UTF-8 BOM. |

## Saved mappings

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `mappings.enabled` | — | true | Off: no options table needed, and saving throws InvalidSavedMappingException::disabled(). |
| `mappings.max_per_importer` | — | 20 | 1 … 1,000 per owner and importer. |
| `mappings.max_bytes` | — | 60000 | 1,024 … 16,000,000 — the options value column is text (64 KB on MySQL). |
| `mappings.lock_store` | `IMPORTS_LOCK_STORE` | blank = default cache store | Serialises saves — use a shared store (redis, database). |

## HTTP

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `resources.owner / actor / record` | — | ImportOwnerResource / ImportActorResource / ImportRecordResource | An instantiable JsonResource subclass for each reference (see JSON API). |
| `routes.enabled` | `IMPORTS_ROUTES` | off | The JSON API — on/off (any boolean). |
| `routes.prefix` | — | api/imports | The URL prefix. |
| `routes.middleware` | — | \['api', 'auth'\] | Route middleware. |
| `routes.upload_limit` | `IMPORTS_UPLOAD_LIMIT` | 10 | 0 … 10,000 uploads per minute per authenticated user (per IP for guests), both upload endpoints; 0 = no limit. |
| `routes.owner_resolver` | — | null = the authenticated user | A Contracts\\ResolvesImportOwner class. |

## Environment

```bash
IMPORTS_KEY_TYPE=bigint
IMPORTS_RECORD_KEY_TYPE=
IMPORTS_PRIMARY_KEY_TYPE=bigint
IMPORTS_DISK=local
IMPORTS_RETENTION=P30D
IMPORTS_TMP_PATH=
IMPORTS_HISTORY_RETENTION=
IMPORTS_MAX_FILE_SIZE=10240
IMPORTS_MAX_ROWS=10000
IMPORTS_MAX_COLUMNS=100
IMPORTS_SYNC_THRESHOLD=500
IMPORTS_UNDO_WINDOW=P7D
IMPORTS_QUEUE_ENABLED=true
IMPORTS_QUEUE_CONNECTION=
IMPORTS_QUEUE_NAME=
IMPORTS_LOCK_STORE=redis
IMPORTS_ROUTES=off
IMPORTS_UPLOAD_LIMIT=10
```

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