PHPackages                             elachagui/laravel-smart-importer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. elachagui/laravel-smart-importer

ActiveLibrary[Database &amp; ORM](/categories/database)

elachagui/laravel-smart-importer
================================

A production-ready Laravel package for importing XLSX and CSV files into Eloquent models with column mapping, validation, duplicate handling, logging, chunking, and queue support.

1.0.0(4mo ago)26MITPHPPHP ^8.1

Since Feb 20Pushed 4mo agoCompare

[ Source](https://github.com/AyyoubElachagui/laravel-smart-importer)[ Packagist](https://packagist.org/packages/elachagui/laravel-smart-importer)[ RSS](/packages/elachagui-laravel-smart-importer/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (10)Versions (2)Used By (0)

Laravel Smart Importer
======================

[](#laravel-smart-importer)

A production-ready Laravel package for importing XLSX and CSV files into Eloquent models with column mapping, validation, duplicate handling, **relational imports**, logging, chunking, and queue support.

Features
--------

[](#features)

- **Multiple File Formats**: Support for CSV, XLSX, and XLS files
- **Dynamic Column Mapping**: Map file columns to model attributes
- **Relational Imports**: Import data with all Laravel Eloquent relation types
- **Validation**: Validate each row using model-defined rules
- **Duplicate Handling**: Skip, update, or fail on duplicate records
- **Chunking**: Process large files in memory-efficient chunks
- **Queue Support**: Process imports asynchronously using Laravel queues
- **Logging**: Track import progress with detailed logs
- **Events**: Listen to import lifecycle events
- **Field Transformers**: Transform data before importing
- **Preview**: Preview file contents before importing
- **Security**: Whitelist allowed models for import

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 10.0+

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require elachagui/laravel-smart-importer
```

Publish the configuration file:

```
php artisan vendor:publish --tag=smart-importer-config
```

Run the migrations:

```
php artisan migrate
```

Configuration
-------------

[](#configuration)

The configuration file `config/smart-importer.php` allows you to customize:

```
return [
    // Models allowed for import (security)
    'allowed_models' => [
        App\Models\User::class,
        App\Models\Product::class,
    ],

    // Default chunk size for processing
    'chunk_size' => 1000,

    // Default duplicate strategy: 'skip', 'update', 'fail'
    'duplicate_strategy' => 'skip',

    // Queue configuration
    'queue' => [
        'enabled' => false,
        'connection' => env('QUEUE_CONNECTION', 'sync'),
        'queue' => 'imports',
    ],

    // Stop on first validation error
    'stop_on_error' => false,

    // Wrap import in database transaction
    'use_transaction' => false,

    // Disable model events during import
    'disable_model_events' => false,

    // File storage configuration
    'storage' => [
        'disk' => 'local',
        'path' => 'imports',
    ],

    // CSV settings
    'csv' => [
        'delimiter' => ',',
        'enclosure' => '"',
        'escape' => '\\',
        'encoding' => 'UTF-8',
    ],

    // Logging
    'logging' => [
        'enabled' => true,
        'channel' => env('LOG_CHANNEL', 'stack'),
    ],
];
```

Basic Usage
-----------

[](#basic-usage)

### Simple Import

[](#simple-import)

```
use Elachagui\SmartImporter\Facades\SmartImporter;

$import = SmartImporter::make()
    ->model(App\Models\User::class)
    ->file($request->file('file'))
    ->mapping([
        'Full Name' => 'name',
        'Email Address' => 'email',
        'Phone Number' => 'phone',
    ])
    ->import();
```

### With Duplicate Handling

[](#with-duplicate-handling)

```
$import = SmartImporter::make()
    ->model(App\Models\User::class)
    ->file($request->file('file'))
    ->mapping([
        'Name' => 'name',
        'Email' => 'email',
    ])
    ->duplicateStrategy('update') // or 'skip', 'fail'
    ->import();

// Or use convenience methods
->skipDuplicates()
->updateDuplicates()
->failOnDuplicates()
```

### With Field Transformers

[](#with-field-transformers)

```
use Carbon\Carbon;

$import = SmartImporter::make()
    ->model(App\Models\Product::class)
    ->file($request->file('file'))
    ->mapping([
        'Product Name' => 'name',
        'Price' => 'price',
        'Created Date' => 'created_at',
    ])
    ->transform([
        'price' => fn($value) => floatval(str_replace(['$', ','], '', $value)),
        'created_at' => fn($value) => Carbon::parse($value),
    ])
    ->import();
```

### Queue Processing

[](#queue-processing)

```
$import = SmartImporter::make()
    ->model(App\Models\User::class)
    ->file($request->file('file'))
    ->mapping([
        'Name' => 'name',
        'Email' => 'email',
    ])
    ->queue() // Use default queue
    // or
    ->queue('redis', 'high-priority') // Custom connection and queue
    ->import();
```

### With Options

[](#with-options)

```
$import = SmartImporter::make()
    ->model(App\Models\User::class)
    ->file($request->file('file'))
    ->mapping([
        'Name' => 'name',
        'Email' => 'email',
    ])
    ->chunkSize(500)
    ->uniqueBy(['email'])
    ->stopOnError()
    ->withTransaction()
    ->withoutModelEvents()
    ->import();
```

### Preview File

[](#preview-file)

```
$preview = SmartImporter::make()
    ->file($request->file('file'))
    ->preview(10);

// Returns:
// [
//     'headers' => ['Name', 'Email', 'Phone'],
//     'rows' => [...],
//     'total_rows' => 100,
// ]
```

### Get File Headers

[](#get-file-headers)

```
$headers = SmartImporter::make()
    ->file($request->file('file'))
    ->getHeaders();

// Returns: ['Name', 'Email', 'Phone']
```

Relational Imports
------------------

[](#relational-imports)

The package supports importing data with all Laravel Eloquent relation types. This allows you to create related records, look up existing records for foreign keys, and sync many-to-many relationships during import.

### Supported Relation Types

[](#supported-relation-types)

Relation TypeDescriptionProcessing Phase`belongsTo`Lookup/create parent record, set foreign keyBefore main model`hasOne`Create single child recordAfter main model`hasMany`Create multiple child recordsAfter main model`belongsToMany`Sync pivot table (many-to-many)After main model`morphOne`Polymorphic one-to-oneAfter main model`morphMany`Polymorphic one-to-manyAfter main model`morphTo`Inverse polymorphic (set type + id)Before main model`morphToMany`Polymorphic many-to-manyAfter main model`morphedByMany`Inverse polymorphic many-to-manyAfter main model`hasOneThrough`Through intermediate modelAfter main model`hasManyThrough`Through intermediate modelAfter main model### BelongsTo Relations

[](#belongsto-relations)

Look up an existing parent record (or create it if missing) and automatically set the foreign key.

```
use App\Models\Product;
use App\Models\Category;

$import = SmartImporter::make()
    ->model(Product::class)
    ->file($file)
    ->mapping([
        'SKU' => 'sku',
        'Name' => 'name',
        'Price' => 'price',
    ])
    ->relations([
        'category' => [
            'type' => 'belongsTo',
            'model' => Category::class,
            'file_column' => 'Category Name',    // Column in CSV/Excel
            'lookup_by' => 'name',               // Column to search in categories table
            'create_if_missing' => true,         // Create category if not found
            'create_mapping' => [                // Optional: map additional fields when creating
                'Category Name' => 'name',
                'Category Slug' => 'slug',
            ],
            'defaults' => [                      // Optional: default values when creating
                'active' => true,
            ],
            'foreign_key' => 'category_id',      // Optional: explicit foreign key (auto-detected)
            'owner_key' => 'id',                 // Optional: parent's key column (default: 'id')
        ],
    ])
    ->import();
```

**Composite Key Lookup:**

```
->relations([
    'warehouse' => [
        'type' => 'belongsTo',
        'model' => Warehouse::class,
        'file_columns' => ['Region Code', 'Warehouse Code'],
        'lookup_columns' => ['region_code', 'code'],
    ],
])
```

### HasOne / HasMany Relations

[](#hasone--hasmany-relations)

Create child records after the main model is saved.

```
use App\Models\Order;
use App\Models\OrderItem;

$import = SmartImporter::make()
    ->model(Order::class)
    ->file($file)
    ->mapping([
        'Order Number' => 'order_number',
        'Total' => 'total',
    ])
    ->relations([
        // HasMany with column mapping
        'items' => [
            'type' => 'hasMany',
            'model' => OrderItem::class,
            'foreign_key' => 'order_id',
            'mapping' => [
                'Item Name' => 'product_name',
                'Quantity' => 'quantity',
                'Unit Price' => 'price',
            ],
        ],
    ])
    ->import();
```

**Multiple Records from Delimited Column:**

```
->relations([
    'variants' => [
        'type' => 'hasMany',
        'model' => ProductVariant::class,
        'foreign_key' => 'product_id',
        'file_column' => 'Sizes',           // e.g., "S,M,L,XL"
        'separator' => ',',
        'mapping' => ['Sizes' => 'size'],
        'defaults' => ['stock' => 0],
    ],
])
```

**Multiple Records from JSON Column:**

```
->relations([
    'items' => [
        'type' => 'hasMany',
        'model' => OrderItem::class,
        'foreign_key' => 'order_id',
        'file_column' => 'Items JSON',
        // File contains: [{"name":"Widget","qty":2},{"name":"Gadget","qty":1}]
    ],
])
```

### BelongsToMany Relations (Many-to-Many)

[](#belongstomany-relations-many-to-many)

Sync pivot table relationships from a column containing multiple values.

```
use App\Models\Product;
use App\Models\Tag;

$import = SmartImporter::make()
    ->model(Product::class)
    ->file($file)
    ->mapping([
        'SKU' => 'sku',
        'Name' => 'name',
    ])
    ->relations([
        'tags' => [
            'type' => 'belongsToMany',
            'model' => Tag::class,
            'file_column' => 'Tags',             // e.g., "electronics,featured,sale"
            'lookup_by' => 'name',               // Find tags by name
            'separator' => ',',                  // How values are separated
            'create_if_missing' => true,         // Create tags that don't exist
            'sync_mode' => 'sync',               // 'sync', 'attach', 'toggle', 'syncWithoutDetaching'
            'pivot_data' => [                    // Optional: data for pivot table
                'assigned_by' => 'import',
            ],
            'pivot_columns' => [                 // Optional: map file columns to pivot columns
                'Tag Priority' => 'priority',
            ],
        ],
    ])
    ->import();
```

**Sync Modes:**

ModeBehavior`sync`Replace all existing relations with new ones`attach`Add new relations without removing existing`toggle`Toggle relations (add if missing, remove if present)`syncWithoutDetaching`Add new relations, keep existing ones### Polymorphic Relations

[](#polymorphic-relations)

**MorphMany - Create polymorphic child records:**

```
use App\Models\Product;
use App\Models\Comment;

$import = SmartImporter::make()
    ->model(Product::class)
    ->file($file)
    ->mapping([
        'SKU' => 'sku',
        'Name' => 'name',
    ])
    ->relations([
        'comments' => [
            'type' => 'morphMany',
            'model' => Comment::class,
            'mapping' => [
                'Review' => 'body',
                'Rating' => 'rating',
            ],
        ],
    ])
    ->import();
```

**MorphTo - Set polymorphic parent:**

```
use App\Models\Comment;
use App\Models\Product;

$import = SmartImporter::make()
    ->model(Comment::class)
    ->file($file)
    ->mapping([
        'Body' => 'body',
    ])
    ->relations([
        'commentable' => [
            'type' => 'morphTo',
            'model' => Product::class,
            'file_column' => 'Product SKU',
            'lookup_by' => 'sku',
            'morph_name' => 'commentable',       // Optional: morph name
            'create_if_missing' => true,
        ],
    ])
    ->import();
```

**MorphToMany - Polymorphic many-to-many:**

```
->relations([
    'tags' => [
        'type' => 'morphToMany',
        'model' => Tag::class,
        'file_column' => 'Tags',
        'lookup_by' => 'name',
        'separator' => ',',
        'create_if_missing' => true,
    ],
])
```

### Through Relations

[](#through-relations)

Create records through an intermediate model.

```
use App\Models\Country;
use App\Models\User;
use App\Models\Post;

$import = SmartImporter::make()
    ->model(Country::class)
    ->file($file)
    ->mapping([
        'Country Name' => 'name',
    ])
    ->relations([
        'posts' => [
            'type' => 'hasManyThrough',
            'model' => Post::class,
            'through_model' => User::class,
            'first_key' => 'country_id',         // Foreign key on User
            'second_key' => 'user_id',           // Foreign key on Post
            'through_mapping' => [               // Create the intermediate User
                'Author Name' => 'name',
                'Author Email' => 'email',
            ],
            'mapping' => [                       // Create the Post
                'Post Title' => 'title',
                'Post Body' => 'body',
            ],
        ],
    ])
    ->import();
```

### Model-Defined Relations

[](#model-defined-relations)

Define relations directly in your model by implementing the `importRelations()` method:

```
use Elachagui\SmartImporter\Contracts\Importable;
use Illuminate\Database\Eloquent\Model;

class Product extends Model implements Importable
{
    public static function importRules(): array
    {
        return [
            'sku' => 'required|string|max:50',
            'name' => 'required|string|max:255',
            'price' => 'required|numeric|min:0',
        ];
    }

    public static function importUniqueBy(): array
    {
        return ['sku'];
    }

    public static function importRelations(): array
    {
        return [
            'category' => [
                'type' => 'belongsTo',
                'model' => Category::class,
                'file_column' => 'category_name',
                'lookup_by' => 'name',
                'create_if_missing' => true,
            ],
            'tags' => [
                'type' => 'belongsToMany',
                'model' => Tag::class,
                'file_column' => 'tags',
                'lookup_by' => 'name',
                'separator' => ',',
                'create_if_missing' => true,
            ],
        ];
    }
}
```

Then import without specifying relations:

```
$import = SmartImporter::make()
    ->model(Product::class)
    ->file($file)
    ->mapping([
        'SKU' => 'sku',
        'Name' => 'name',
        'Price' => 'price',
    ])
    ->import(); // Relations are auto-loaded from model
```

### Combining Multiple Relations

[](#combining-multiple-relations)

```
$import = SmartImporter::make()
    ->model(Order::class)
    ->file($file)
    ->mapping([
        'Order Number' => 'order_number',
        'Total' => 'total',
    ])
    ->relations([
        // BelongsTo: Set customer_id
        'customer' => [
            'type' => 'belongsTo',
            'model' => Customer::class,
            'file_column' => 'Customer Email',
            'lookup_by' => 'email',
            'create_if_missing' => true,
            'create_mapping' => [
                'Customer Email' => 'email',
                'Customer Name' => 'name',
            ],
        ],
        // HasMany: Create order items
        'items' => [
            'type' => 'hasMany',
            'model' => OrderItem::class,
            'foreign_key' => 'order_id',
            'mapping' => [
                'Product' => 'product_name',
                'Qty' => 'quantity',
                'Price' => 'price',
            ],
        ],
        // BelongsToMany: Sync tags
        'tags' => [
            'type' => 'belongsToMany',
            'model' => Tag::class,
            'file_column' => 'Tags',
            'lookup_by' => 'name',
            'separator' => ',',
            'create_if_missing' => true,
        ],
        // MorphMany: Add comments
        'comments' => [
            'type' => 'morphMany',
            'model' => Comment::class,
            'mapping' => [
                'Note' => 'body',
            ],
        ],
    ])
    ->import();
```

### Relation Configuration Reference

[](#relation-configuration-reference)

OptionTypeDescription`type`string**Required.** Relation type (belongsTo, hasMany, etc.)`model`string**Required.** Related model class`file_column`stringSingle file column to read from`file_columns`arrayMultiple file columns (for composite keys)`lookup_by`stringColumn to search by in related table`lookup_columns`arrayMultiple columns for composite key lookup`create_if_missing`boolCreate related record if not found (default: false)`create_mapping`arrayColumn mapping when creating related record`mapping`arrayColumn mapping for hasOne/hasMany child records`defaults`arrayDefault values when creating records`foreign_key`stringExplicit foreign key column name`owner_key`stringParent model's key column (default: 'id')`local_key`stringLocal model's key column (default: 'id')`separator`stringSeparator for parsing multiple values (default: ',')`sync_mode`stringFor belongsToMany: sync, attach, toggle (default: 'sync')`pivot_data`arrayStatic data for pivot table`pivot_columns`arrayMap file columns to pivot columns`morph_name`stringMorph name for polymorphic relations`morph_type_column`stringMorph type column name`morph_id_column`stringMorph ID column name`through_model`stringIntermediate model for through relations`first_key`stringFirst key for through relations`second_key`stringSecond key for through relations`transformers`arrayField transformers for this relationImplementing Importable Interface
---------------------------------

[](#implementing-importable-interface)

For models with validation rules, implement the `Importable` interface:

```
use Elachagui\SmartImporter\Contracts\Importable;
use Illuminate\Database\Eloquent\Model;

class User extends Model implements Importable
{
    public static function importRules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'phone' => 'nullable|string|max:20',
        ];
    }

    public static function importUniqueBy(): array
    {
        return ['email'];
    }

    // Optional: Define relations for import
    public static function importRelations(): array
    {
        return [
            // ... relation definitions
        ];
    }
}
```

Import Model
------------

[](#import-model)

The package creates import records to track progress:

```
use Elachagui\SmartImporter\Models\Import;

$import = Import::find($id);

// Access import data
$import->model;           // Target model class
$import->file_path;       // Path to imported file
$import->total_rows;      // Total rows in file
$import->processed_rows;  // Rows processed
$import->success_rows;    // Successfully imported
$import->failed_rows;     // Failed rows
$import->skipped_rows;    // Skipped (duplicates)
$import->status;          // pending, processing, completed, failed, cancelled
$import->mapping;         // Column mapping (array)
$import->started_at;      // When import started
$import->finished_at;     // When import finished

// Check status
$import->isPending();
$import->isProcessing();
$import->isCompleted();
$import->isFailed();
$import->isCancelled();

// Get progress
$import->getProgressPercentage(); // Returns 0-100

// Get errors
$import->errors; // HasMany relationship to ImportError
```

Cancelling Imports
------------------

[](#cancelling-imports)

```
$import = SmartImporter::find($id);

SmartImporter::cancel($import);
```

Events
------

[](#events)

The package fires several events during the import lifecycle:

```
use Elachagui\SmartImporter\Events\ImportStarted;
use Elachagui\SmartImporter\Events\ImportCompleted;
use Elachagui\SmartImporter\Events\ImportFailed;
use Elachagui\SmartImporter\Events\RowImported;

// In your EventServiceProvider
protected $listen = [
    ImportStarted::class => [
        SendImportStartedNotification::class,
    ],
    ImportCompleted::class => [
        SendImportCompletedNotification::class,
    ],
    ImportFailed::class => [
        SendImportFailedNotification::class,
    ],
    RowImported::class => [
        ProcessImportedRow::class,
    ],
];
```

### Event Data

[](#event-data)

```
// ImportStarted
public Import $import;

// ImportCompleted
public Import $import;
public function getSummary(): array; // Returns summary data

// ImportFailed
public Import $import;
public Throwable $exception;
public function getErrorMessage(): string;
public function getErrorDetails(): array;

// RowImported
public Import $import;
public int $rowNumber;
public array $data;
public string $action; // 'created' or 'updated'
public function wasCreated(): bool;
public function wasUpdated(): bool;
```

Exception Handling
------------------

[](#exception-handling)

```
use Elachagui\SmartImporter\Exceptions\ImportException;

try {
    $import = SmartImporter::make()
        ->model(App\Models\User::class)
        ->file($file)
        ->mapping($mapping)
        ->import();
} catch (ImportException $e) {
    // Handle import-specific errors
    $message = $e->getMessage();
    $rowNumber = $e->getRowNumber();
    $rowData = $e->getRowData();
    $context = $e->getContext();
}
```

Example Controller
------------------

[](#example-controller)

```
