Skip to content

Migration Assistant Extension Points

Migration Assistant imports, exports, reviews, and rolls back content moves between Capell sites. Extension points should describe what a package owns, not patch the import job from the outside.

NeedExtension point
Read a new source formatImportSourceReader registered in ImportSourceRegistry
Add import targetsImportTargetRegistry::register()
Resolve imported relationsRelationMatchResolverRegistry::register()
Contribute review rowsMigrationAssistantRowContributor
Resolve package contextMigrationAssistantContextResolver
Detect URL/page collisionsPageCollisionDetector
Resolve page import targetPageImportTargetResolver
Mark relation ownershipOwnershipMap::register()

The package ships CSV and XML readers, default targets for page, type, and collection, and relation resolvers for layouts, types, sites, and media.

use Capell\MigrationAssistant\Contracts\ImportSourceReader;
use Capell\MigrationAssistant\Data\ExternalImportReadResult;
use Capell\MigrationAssistant\Support\ImportSourceRegistry;
final class JsonImportSourceReader implements ImportSourceReader
{
public function supports(string $extension): bool
{
return $extension === 'json';
}
public function read(string $path): ExternalImportReadResult
{
$payload = json_decode((string) file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
return new ExternalImportReadResult(
sourceType: 'json',
columns: array_keys($payload[0] ?? []),
rows: $payload,
metadata: ['filename' => basename($path)],
);
}
}
$this->app->afterResolving(ImportSourceRegistry::class, static function (ImportSourceRegistry $registry): void {
$registry->register(new JsonImportSourceReader, prepend: true);
});

Use prepend: true only when the new reader should win over a built-in reader for the same extension.

use Capell\MigrationAssistant\Support\ImportTargetRegistry;
use Vendor\KnowledgeBase\Models\Article;
$this->app->afterResolving(ImportTargetRegistry::class, static function (ImportTargetRegistry $registry): void {
$registry->register('knowledge_article', Article::class);
});

Target keys become part of import payloads. Keep them stable once exported.

Relation resolvers are checked in priority order for each group.

use Capell\MigrationAssistant\Services\Import\Resolvers\KeyedMatchResolver;
use Capell\MigrationAssistant\Services\Import\Resolvers\RelationMatchResolverRegistry;
use Vendor\KnowledgeBase\Models\Category;
$this->app->afterResolving(RelationMatchResolverRegistry::class, static function (RelationMatchResolverRegistry $registry): void {
$registry->register('knowledge_categories', new KeyedMatchResolver(Category::class, keyColumn: 'slug'));
});

Use package-specific group names unless the resolver intentionally contributes to a core group such as layouts, types, sites, or media.

KeyUse
migration-assistant.enabledEnables the package surface.
migration-assistant.diskStorage disk for import/export files.
migration-assistant.channelsNotification channels for completed or failed imports.
migration-assistant.completedCompletion notification settings.
migration-assistant.failedFailure notification settings.
migration-assistant.connectionQueue/database connection setting where configured.

The package also has model, table, and path config used by import internals. Document those only when a host app needs to change them.

Terminal window
vendor/bin/pest packages/migration-assistant/tests --configuration=phpunit.xml