Import and export Resource data
Declare transfer columns and verify scope, validation, and import results.
Import and export Resource data
Start with the tenant-owned Example from Create development data. Its normal create path needs only a name; organization-owned Resources require additional target handling.
Declare safe columns
Add these imports and members to src/Models/Example.php. Do not replace the whole generated model:
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Nexia\Laravel\ResourceTransfer\Concerns\ExportImportable;
use Nexia\ResourceTransfer\TransferSchema;
// Add inside the generated tenant-owned Example model; retain its other traits.
use ExportImportable;
public static function transferColumns(TransferSchema $schema): TransferSchema
{
return $schema->field('name', 'workshop.example.name.label', required: true);
}
public static function transferExportQuery(?Authenticatable $actor = null): Builder
{
abort_if($actor === null, 403);
return static::query()->visibleTo($actor);
}The default transfer scope is tenant. The generated controller already emits meta.resource_transfer; preserve it and the list's transfer integration. The host handles file formats and the App handles its own data. Do not expose numeric keys, tenant IDs, secrets or unrelated App fields as editable columns.
Verify through the list
- Synchronize using project
nexia dev. - Give the test actor tenant-level
system.data.export/system.data.importand the appropriate Example read/create permissions. - Open Examples. Export a small authorized set and compare it with the list; records hidden by Policy must stay absent.
- Download the offered import template, add one synthetic name, preview and execute the import. Reload the list and check per-row results.
- Check empty required names, denied users and duplicates. Import is create-only, not an upsert. A field declared with
TransferSchema::key()identifies duplicates;nameabove is deliberately not unique.
Default limits are 10,000 exported rows and 1,000 imported rows per model declaration. Read the returned metadata for the active limits and formats. A partial import result is not an all-or-nothing business transaction; use returned row outcomes for correction.
Preserve your domain rules
createFromImport(array $attributes) is the App-owned creation hook. Override it when normal creation needs additional validation, derived values or related writes; reuse the same domain operation as the API. The default simply creates the model. Request-only validation is not automatically executed here.
For legal-entity-owned models, set transferScope() to self::TRANSFER_SCOPE_LEGAL_ENTITY, constrain transferExportQuery() to authorized records, and derive the import owner from trusted scope. Do not accept arbitrary organization IDs from a spreadsheet. Test cross-organization access before exposing transfer.
TransferSchema supports field, key, references and export restrictions. Custom export sources implement the SDK ResourceTransferExportSourceContribution and ResourceTransferExportSource contracts; their queries still need actor authorization. Use the installed SDK definitions for exact overloads.
Publish Data Center choices without loading App code
Apps contributing Data Center stages must keep their discovery declarations in
resources/data-migrations.json. This file contains providers, targets, and
stages arrays using the existing DataMigration*Registration fields, except
that stages omit loader. Set each declaration's ownerAppKey to your App key.
Keep translation keys in the metadata and their values in your normal locale catalogs.
Import this same JSON in the App's registration module and attach the stage's
existing lazy loader there; do not maintain a second copy of the labels,
permissions, prerequisites or file guides.
The runtime exporter includes this file and its referenced labels in the reviewed catalog. An absent file declares no Data Center contributions. Core reads the selected tenant catalogs in one request and activates only the App needed for the selected stage. This metadata grants no permission to import; stage execution still passes through the current runtime and business authorization checks.
After changing these declarations, rebuild and submit a new App version for review. Existing accepted catalogs are immutable; a platform update does not update your App's declarations. Older catalogs without this metadata show an update notice. Verify the choices again after the updated App version becomes available in the workspace.
Keep domain imports in an App pipeline
For imports with domain validation or approval, implement Nexia\ResourceImport\Contracts\ResourceImportPipelineContribution and return ResourceImportPipelineDefinition entries from resourceImportPipelines(). Each definition supplies the Resource key, transfer schema, App-owned handler class, permission and supported source profiles. The handler implements ResourceImportPipeline: receive, validateRows, apply and retry.
Keep authorization, transactions, duplicate detection and domain rejection in that handler. Report rejected rows in the batch result rather than silently falling back to generic create calls. Repeating receive with the same idempotency key and matching file/mapping/profile must return the existing batch; different content with that key is a conflict. apply commits only a validated, eligible batch.
Verify the App's pipeline appears in the workspace's Data Migration selection, then test a valid file, a rejected row and a repeated receive request. A local declaration or a completed upload does not prove that data was applied. Preserve the same actor and organization constraints throughout validation and application.
See the public CLI documentation for command and option details.
Shared import workspace recovery
The shared Dataset Import workspace shows waiting separately and preserves progress across preparation and validation. Upload again clears the previous review and returns to file selection without applying or deleting business records. Loading failures offer retry; SDK compatibility errors require a compatible App frontend build.
Incremental pipeline execution
IncrementalResourceImportPipeline is an optional extension to the chunked import contract. A compatible queued host calls validateNext and applyNext, then schedules another invocation only after a confirmed pending result. Existing pipelines and nonqueued callers retain their existing methods. Adopt the matching PHP SDK, Core migration/consumer, and native runtime together before enabling an App; use PHP SDK 0.10.16, Core 0.8.170 and the matching tenant migrations and Manager runtime.
validateNext receives a SeekableResourceImportRowSource. Use slice($offset, $limit) for bounded reads; its keys are zero-based positions in the complete source, while __line remains the original spreadsheet line. Store the cursor in App-owned persistence. Commit each unit's output and cursor in one transaction, and return new ImportPipelineStep(null, $processed, $total) while work remains. Return a step containing the final ImportBatchState only after the complete validation or application has finished. Totals may include multiple validation passes.
Keep duplicate detection across the entire file, and keep partial validation ineligible for apply. Restore and check the actor, company, mapping and source identity on every invocation. During apply, persist business writes and the apply cursor atomically, and recheck the reviewed references before writing. A lost or uncertain work result is stopped for review; the host does not assume that replaying it is safe. Cancellation prevents subsequent units and does not undo committed business rows.
The host retains one mapped source across these invocations, so Apps do not ask users to split a supported file. This capability does not raise upload, cell, memory, retention or overall execution limits. Verify cross-window duplicates, restart after a committed unit, concurrent wakeups, revoked permissions, cancellation, changed runtime identity and response loss before release.
Paginated result details
A pipeline can declare resultRowsPath: '/api/my-app/imports/{batch}/result-rows' on ResourceImportPipelineDefinition. This optional App-owned GET route receives status (unchanged, error, conflict, skipped, or not_processed), page (starting at 1), per_page (at most 100), and the run's legal entity. Return {data, total, page, per_page}; data contains the same row objects as ImportRowDisposition::toArray(). Authorize the current actor, entity, and original batch owner on every request. Persist the full results in the App and read only the requested page; do not expand rowDispositions, which remains a preview. The host resolves the route from the current catalog, never from a browser-supplied URL, and only exposes results to the operator who created the migration run. A compatible SDK/runtime must be installed before declaring this field.
For an interrupted apply, return actual committed create/update outcomes,
skipped for excluded rows, and not_processed for uncommitted rows (including
rolled-back rows). Include dispositionCounts['not_processed'] even when zero:
this marks failed-stage counts as settled outcomes rather than preview intentions.
Do not rewrite an exclusion with an unknown historical reason as a known cause.
A result-page response may include a summary object with nonnegative integer
rows, saved, unchanged, skipped, errors, conflicts, not_processed, and
boolean stopped. Core can use it to refresh retained result screens without
loading all row contents.
Resumable pipeline checkpoints
Before enabling the pipeline, verify restart between committed checkpoints, cross-window duplicates, repeated apply, revoked authority, cancellation, denied foreign batches and response loss. A pending checkpoint is not a validated batch, and a queued continuation is not business completion. Use the incremental execution and result-page contracts above; keep the same actor, organization and source identity throughout.
See the published PHP contracts and PHP SDK 0.10.16 release note. Existing synchronous pipelines remain supported; the new contract does not raise upload limits.