Skip to content
Guide

Resource Transfer

Publish Resource export sources and choose a validated recipe or pipeline for App-owned import behavior.

Add import or export to an App Resource using the SDK transfer contracts and the shared host workspace.

Apps can reuse the common Import/Export foundation directly. Core supplies formats, upload, mapping, preview, orchestration, polling and common UI, and enforces declared permissions. The App owns schema, permission declarations, scope filtering, export rows, domain validation, persistence and retry rules. Common Core gates do not replace the App's row visibility or business-write authorization.

Connect the transfer operation

For export, declare ResourceTransferExportSourceContribution with a TransferSchema, explicit scope, required permissions, and an App-owned source class. Its query must select only actor-authorized records and redact protected fields. The People Operating Unit example below keeps those decisions in People.

For import, choose a recipe for a declarative row workflow or ResourceImportPipelineContribution for a custom analysis/preview/execution pipeline. Declare permissions and upload/schema limits before exposing the action. Reauthorize execution and every resulting domain mutation; a successful preview is not lasting write authority.

Confirm the result

An authorized actor can export only the intended scope, or preview and apply a valid import; malformed input, forbidden records, and duplicate execution are refused or safely reconciled. Follow Test an App for package checks.

Export, import, and background execution

Resource Transfer is the platform path for reviewed export and import. An App declares an export source and, when generic Resource creation is not the correct write boundary, an import recipe or pipeline. The host owns file handling and orchestration; the declared source or pipeline keeps App authorization and domain rules.

packages/people/src/Contribution/OperatingUnitWorkerExportContribution.php publishes a real export-only projection:

Code example
PHP
use Amuzcorp\Nexia\PeopleCore\Domain\WorkerDirectory\OperatingUnitWorkerDirectoryExportSource;
use Nexia\ResourceTransfer\Contracts\ResourceTransferDefinition;
use Nexia\ResourceTransfer\ResourceTransferExportSourceDefinition;
use Nexia\ResourceTransfer\TransferSchema;

new ResourceTransferExportSourceDefinition(
    resourceKey: 'people.operating_unit_worker',
    labelKey: 'people.operating_unit_worker.resource_label',
    sourceClass: OperatingUnitWorkerDirectoryExportSource::class,
    schema: TransferSchema::make()
        ->exportOnly('worker_number', 'people.workforce_export.columns.worker_number')
        ->exportOnly('display_name', 'people.workforce_export.columns.display_name'),
    scope: ResourceTransferDefinition::SCOPE_OPERATING_UNIT,
    permissionKeys: [
        'people.operating_unit_worker.read',
        'people.workforce_export.execute',
    ],
);

This is a constructor excerpt. Return it from ResourceTransferExportSourceContribution::resourceTransferExportSources() under a manifest contribution location. Publish the listed permissions through PermissionContribution; sourceClass implements the row source below. The full People contributor shows both interfaces.

How it fits: Choose the owner of each stage

StageApp suppliesHost supplies
ExportSchema, permissions, authorized lazy rows, scope filtering, audit evidenceDeclared permission gates, formats, row limit, file response
Generic importResource schema and declared create actionMapping, review, and authorized create calls
Recipe importDataset graph and providersTransactional prerequisite execution through Resource actions
Pipeline importSchema, permissions, validation, batch lifecycle, business writes and retry rulesUpload, mapping, preview, confirmation, orchestration and polling

Export contribution

ResourceTransferExportSourceDefinition names resourceKey, labelKey, sourceClass, schema, scope, and permissionKeys. formats defaults to CSV, XLSX, JSON, and text; rowLimit defaults to 10,000. Narrow formats when they cannot represent the data and choose the row limit from streaming cost, not convenience. Use a dedicated export permission rather than reusing read.

The source implements the exact SDK contract:

Code example
PHP
interface ResourceTransferExportSource
{
    public function rows(ResourceTransferExportRequest $request): iterable;
    public function evidence(ResourceTransferExportRequest $request): array;
}

Yield rows or return a lazy collection. Inside rows(), apply the actor's record visibility and the resolved Legal Entity or Operating Unit scope from the request, and honor its selected columns and effective row limit. evidence() records the actual query shape, filters, and scope.

Import recipe or pipeline

TrackChoose it whenCommit owner
Generic Resource TransferOne Resource has a safe match key and declared create actionCore
ImportRecipeContributionOne file spans Resources without an App batch state machineCore through declared Resource actions
ResourceImportPipelineContributionThe App owns batch state, tolerance, frozen evidence, or retryApp pipeline

A recipe declares Resource keys and provider contracts, never Core model classes. Core authorizes each action and executes its prerequisite graph inside host transaction and ownership gates.

A pipeline definition adds a container-resolved ResourceImportPipeline. receive(), validateRows(), apply(), and retry() preserve the App lifecycle. Respect ImportBatchState.applyEligible, frozen normalized rows, rejections, and reviewable fill proposals. ImportSourceProfile separates named vendor workbook shapes; the selected profile replaces definition-wide column aliases rather than blending them.

For large workbooks, implement ChunkedResourceImportPipeline and process the repeatable ResourceImportRowSource with chunks(). Do not materialize the whole source. Bounded rowDispositions may omit per-row history while exact dispositionCounts retain totals. Existing pipelines may keep validateRows().

An ImportPlan may request a MultipleChoiceDecision over unique ChoiceOption values. The host rejects unknown or duplicate replies and returns the accepted subset in declaration order.

Use TransferSchema::reference() for a printed value that identifies another Resource. The host resolves it before approval and the pipeline rechecks the identifier in its Legal Entity boundary. A custom bounded bulk resolver calls ResourceReferences::resolveMany() once; the dispatcher uses the owner's batch capability or falls back to authorized single resolution. Do not build an unbounded N+1 loop.

Custom background import capabilities

AuthorizedImportFileStore::claim() binds a clean upload to actor, Legal Entity, and Resource. find() reauthorizes AuthorizedImportFile metadata; withLocalCopy() supplies a checksum-verified temporary path and deletes it after the callback. It exposes no host storage coordinates or whole-file bytes.

BackgroundOperationStore supplies actor-owned transient tickets through open(), progress(), complete(), fail(), and failIfPending(). Neither capability replaces pipeline authorization, idempotency, or transactions.

Declaration and frontend gates

Run ImportContributionValidator::recipes(), pipelines(), or portfolio() in the App package. Core repeats intrinsic validation during discovery and adds host facts such as active permissions, Resource ownership, and operational App state. .nexia/resource-import-coverage.json must classify every installed App; php artisan nexia-resources:validate-resource-import-coverage rejects omissions or runtime drift.

For a mapped human import, mount NxDatasetImportWorkspace with one resourceKey. It owns upload, mapping, preview, commit, and ticket polling but does not take the selected recipe or pipeline's write authority. Dataset Import is not an Agent capability; agents continue through generic data.* contracts.

Connect the shared frontend

Import components from @nexia/sdk/host and props types from @nexia/sdk.

Public componentUseApp input
NxResourceTransferActionsNavigate from available actions to the common transfer screentransferMeta from an authorized Resource response and the required onImported prop (navigation does not invoke it)
NxResourceImportDialogGeneric Resource import dialog with upload, preview, execution and statusopen, onClose, transfer URLs and completion callback
NxDatasetImportWorkspaceRecipe/pipeline workspace for mapping, preview, commit and pollingresourceKey; organization context comes from the host
NxDatasetExportWorkspaceContributed dataset metadata, format/column selection and exportresourceKey and permitted organization target context

Availability follows server metadata and execution gates; mounting a component grants no authority. See React components and hooks for props and related registries.

Boundaries

  • Export is a protected bulk action, not ordinary list read access.
  • The row limit is a truncation guardrail, not pagination; report when it is hit.
  • Scope filtering belongs inside rows() even though the request carries the resolved scope.
  • Audit evidence describes the query actually executed, not a generic event label.
  • A pipeline is not permission to bypass host upload, review, or operational gates.
  • Saved mapping reuse covers only the same Resource, header shape, source profile, and schema version; decisions, fills, references, and exclusions remain current-file facts.
Source of truth: docs/developers/content/en/platform-extensions/resource-transfer.md