Importers

Defining Importers

Learn how to create and register importers in Tapix.

Importers are the core building blocks of Tapix. Each importer class maps a CSV file to an Eloquent model, defining which fields can be imported, how rows are validated, and how data is saved.

Creating an Importer

Use the Artisan command to scaffold a new importer:

Terminal
php artisan make:tapix-importer {name}
Importers are placed in app/Importers/ by default. You can customize the location via the importer_directories and importer_namespace options in the Tapix config file.

For example, php artisan make:tapix-importer Contact generates the following:

app/Importers/ContactImporter.php
namespace App\Importers;

use Tapix\Core\Fields\ImportField;
use Tapix\Core\Fields\ImportFieldCollection;
use Tapix\Core\Importing\BaseImporter;

final class ContactImporter extends BaseImporter
{
    public function model(): string
    {
        return \App\Models\Contact::class;
    }

    public function fields(): ImportFieldCollection
    {
        return ImportFieldCollection::make([
            ImportField::make('name')->required(),
        ]);
    }
}

Required Methods

Every importer must implement two methods:

model(): string

Returns the fully qualified class name of the Eloquent model that imported rows will be saved to.

app/Importers/ContactImporter.php
public function model(): string
{
    return \App\Models\Contact::class;
}

fields(): ImportFieldCollection

Defines the importable fields. Each field maps a CSV column to a model attribute. See the Fields documentation for the full API.

app/Importers/ContactImporter.php
public function fields(): ImportFieldCollection
{
    return ImportFieldCollection::make([
        ImportField::make('name')->required(),
        ImportField::make('email')->required(),
    ]);
}

Optional Methods

BaseImporter provides sensible defaults for all optional methods. Override them to customize behavior.

label(): string

The display name shown in the Tapix UI. Defaults to the pluralized model basename (e.g., Contact becomes Contacts).

app/Importers/ContactImporter.php
public function label(): string
{
    return 'Company Contacts';
}

icon(): string

The Heroicon name used in navigation. Defaults to heroicon-o-table-cells.

app/Importers/ContactImporter.php
public function icon(): string
{
    return 'heroicon-o-users';
}

Groups the importer under a Filament navigation group. Returns null by default (no grouping).

app/Importers/ContactImporter.php
public function navigationGroup(): ?string
{
    return 'CRM';
}

canImport(): bool

Controls access to the importer. Return false to hide it from users who should not have access. Defaults to true.

app/Importers/ContactImporter.php
public function canImport(): bool
{
    return auth()->user()->can('import contacts');
}

matchableFields(): array

Defines which fields are used to match imported rows against existing records in the database. This controls the update-or-create behavior. Defaults to [MatchableField::id()].

app/Importers/ContactImporter.php
use Tapix\Core\Matching\MatchableField;

public function matchableFields(): array
{
    return [
        MatchableField::id(),
        MatchableField::email('email'),
    ];
}

prepareForSave(array $data, ?Model $existing, array &$context): array

Transforms the validated row data before it is persisted. The $existing parameter contains the matched model instance when updating, or null when creating. The $context array provides metadata including tenant_id and creator_id.

Return the modified data array.

app/Importers/ContactImporter.php
public function prepareForSave(array $data, ?Model $existing, array &$context): array
{
    $data['imported_at'] = now();
    $data['imported_by'] = $context['creator_id'];

    return $data;
}

Registering Importers

Tapix offers several ways to register your importers.

Via the Filament Plugin

Register importers explicitly when configuring the Tapix panel plugin:

app/Providers/Filament/AdminPanelProvider.php
use Tapix\TapixPlugin;

TapixPlugin::make()->importers([
    ContactImporter::class,
    ProductImporter::class,
]);

Auto-Discovery via the Plugin

Let Tapix scan the app/Importers/ directory automatically:

app/Providers/Filament/AdminPanelProvider.php
TapixPlugin::make()->discoverImporters();

Manual Registration

Register importers anywhere in your application (e.g., a service provider):

app/Providers/AppServiceProvider.php
use Tapix\Tapix;

Tapix::registerImporters([
    ContactImporter::class,
    ProductImporter::class,
]);

Standalone Auto-Discovery

Scan and register importers outside of the Filament plugin:

app/Providers/AppServiceProvider.php
Tapix::discoverImporters();

Full Example

Here is a complete importer for a CRM contacts table with multiple field types, relationship linking, and custom access control:

app/Importers/ContactImporter.php
namespace App\Importers;

use App\Models\Company;
use App\Models\Contact;
use Tapix\Core\Enums\FieldType;
use Tapix\Core\Enums\MatchBehavior;
use Tapix\Core\Fields\ImportField;
use Tapix\Core\Fields\ImportFieldCollection;
use Tapix\Core\Importing\BaseImporter;
use Tapix\Core\Matching\MatchableField;

final class ContactImporter extends BaseImporter
{
    public function model(): string
    {
        return Contact::class;
    }

    public function label(): string
    {
        return 'Contacts';
    }

    public function icon(): string
    {
        return 'heroicon-o-users';
    }

    public function navigationGroup(): ?string
    {
        return 'CRM';
    }

    public function canImport(): bool
    {
        return auth()->user()->can('import contacts');
    }

    public function matchableFields(): array
    {
        return [
            MatchableField::id(),
            MatchableField::email('email'),
        ];
    }

    public function fields(): ImportFieldCollection
    {
        return ImportFieldCollection::make([
            ImportField::id(),
            ImportField::make('first_name')
                ->label('First Name')
                ->required()
                ->guess(['fname', 'first', 'given name']),
            ImportField::make('last_name')
                ->label('Last Name')
                ->required()
                ->guess(['lname', 'surname', 'family name']),
            ImportField::make('email')
                ->type(FieldType::Email)
                ->required()
                ->guess(['email address', 'e-mail']),
            ImportField::make('phone')
                ->type(FieldType::Phone)
                ->guess(['telephone', 'mobile', 'cell']),
            ImportField::make('company')
                ->relationship(
                    name: 'company',
                    model: Company::class,
                    matchBy: ['name'],
                    behavior: MatchBehavior::MatchOrCreate,
                )
                ->guess(['company', 'organization', 'org']),
            ImportField::make('birth_date')
                ->type(FieldType::Date)
                ->guess(['birthday', 'date of birth', 'dob']),
            ImportField::make('is_active')
                ->type(FieldType::Boolean)
                ->guess(['active', 'status']),
        ]);
    }

    public function prepareForSave(array $data, ?Model $existing, array &$context): array
    {
        $data['tenant_id'] = $context['tenant_id'];

        return $data;
    }
}
Copyright © 2026