Defining Importers
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:
php artisan make:tapix-importer {name}
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:
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.
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.
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).
public function label(): string
{
return 'Company Contacts';
}
icon(): string
The Heroicon name used in navigation. Defaults to heroicon-o-table-cells.
public function icon(): string
{
return 'heroicon-o-users';
}
navigationGroup(): ?string
Groups the importer under a Filament navigation group. Returns null by default (no grouping).
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.
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()].
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.
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:
use Tapix\TapixPlugin;
TapixPlugin::make()->importers([
ContactImporter::class,
ProductImporter::class,
]);
Auto-Discovery via the Plugin
Let Tapix scan the app/Importers/ directory automatically:
TapixPlugin::make()->discoverImporters();
Manual Registration
Register importers anywhere in your application (e.g., a service provider):
use Tapix\Tapix;
Tapix::registerImporters([
ContactImporter::class,
ProductImporter::class,
]);
Standalone Auto-Discovery
Scan and register importers outside of the Filament plugin:
Tapix::discoverImporters();
Full Example
Here is a complete importer for a CRM contacts table with multiple field types, relationship linking, and custom access control:
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;
}
}