[object Object]

Data management packages in Dynamics 365 Finance & Operations are how you move data between environments — chart of accounts, customer master, parameters. They are also how teams accumulate technical debt. The package that built UAT last quarter is missing two entities that were added in dev last week. Imports fail. The fix-it-now mentality copies the new entities by hand and the next deployment is even more divergent. Versioning fixes this.

What a data management package is

A package is a zip containing a manifest and a set of entity definitions with their data files. The Data Management workspace exports the package and imports it. Each entity in the package maps to a data entity (the F&O abstraction layer over tables) and a source data format.

The mental model: a package is a build artifact. Treat it like a tagged release of a piece of software.

The drift pattern

  • Developer A adds entity X to a package in dev.
  • The package is exported and pushed to UAT.
  • Developer B, separately, modifies the same package in dev to fix entity Y.
  • A second export goes to UAT. Now the UAT package is “the latest” and includes both changes — but the original A change has no audit trail.
  • Production import fails because the package’s entity ordering changed.

Without version discipline, this happens monthly.

Version the package, not the environment

Each package has a name. Add a version suffix in the name: Master_CustomerData_v1.4.2. The version is checked in to source. The package zip is built from source via a pipeline. Environments import a specific version.

The package source layout:

master-customer-data/
  manifest.xml
  entities/
    CustGroup.xml
    CustTable.xml
    CustTrans.xml
  data/
    CustGroup.csv
    CustTable.csv
  version.txt   # 1.4.2
  CHANGELOG.md

A build script zips this into a package and tags the artifact with the version.

The build script

The Data Management framework expects a specific structure. The build script just zips correctly and signs the manifest:

param([string]$Version)

$src = "src/packages/master-customer-data"
$out = "dist/Master_CustomerData_v$Version.zip"

# Stamp version into manifest
$manifest = Get-Content "$src/manifest.xml" -Raw
$manifest = $manifest -replace '<Version>.*</Version>', "<Version>$Version</Version>"
Set-Content "$src/manifest.xml" $manifest

# Zip
Compress-Archive -Path "$src/*" -DestinationPath $out -Force

Write-Host "Built $out"

Run it from CI on tag push. The output zip is the artifact. Store it in your artifact store.

Import via DMF REST endpoint

Manual import via the UI is fine for ad hoc work. For environment promotion, use the Data Management Framework REST endpoint. Authenticate with a service principal, upload the package, trigger the import, poll for completion.

async function importPackage(envBase, token, packageBlob, packageName) {
  // 1. Get blob upload URL
  const urlResp = await fetch(
    `${envBase}/api/connector/enqueue/${packageName}?entity=...&overwrite=true`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/octet-stream'
      },
      body: packageBlob
    }
  );
  const { value: executionId } = await urlResp.json();

  // 2. Poll for completion
  while (true) {
    const status = await fetch(
      `${envBase}/api/connector/jobstatus/${executionId}`,
      { headers: { Authorization: `Bearer ${token}` } }
    );
    const { status: state } = await status.json();
    if (state === 'Succeeded') return executionId;
    if (state === 'Failed') throw new Error('Import failed');
    await new Promise(r => setTimeout(r, 10000));
  }
}

The version table inside F&O

Maintain a custom table cdm_packageinstall inside F&O. Each row records which package version was imported, when, by whom, with what result. The import pipeline writes this row on success. The audit-defense file writes itself.

CustomTable: cdm_packageinstall
  PackageName    : string
  Version        : string
  ImportedAt     : datetime
  ImportedBy     : string (principal)
  RecordCount    : integer
  Status         : enum (Succeeded, PartialSuccess, Failed)
  Notes          : string

When an import fails or partially succeeds, the row records the executionId so you can trace back via DMF logs.

Entity ordering matters

Some entities have dependencies — customer group must exist before customer. The package manifest declares ordering. If you add an entity in dev without updating the order, the import succeeds in dev (where the depended-upon row already exists) and fails in a fresh environment.

Always test the package against a fresh environment as part of the build. The build pipeline:

  1. Provision a fresh tier-1 environment.
  2. Apply the LCS-managed base data.
  3. Import the package.
  4. Run a smoke test (a known-good data sample).
  5. Tear down.

If any step fails, the package is not promoted past tier-1. Expensive but correct.

What about staging tables

Each entity import lands first in a staging table, then projects into the live tables. Staging is where most failures live. The staging table for entity CustCustomerV3Entity is CustCustomerV3Staging. When an import fails, query staging for rows with Status = Error and inspect ErrorMessage. The error messages are not great. They are still better than the package-level status.

Configuration vs data packages

There is a meaningful distinction inside the same tooling:

  • Configuration packages: parameters, setup data, low-volume reference data. Versioned tightly, source-of-truth in git.
  • Data packages: business data movement, often one-shot per environment refresh. Version less rigidly, but always tag the source environment and timestamp.

The discipline differs because the artifacts differ. Conflating them is a common mistake.

See also

Dual-write replication lag monitoring — the dual-write side of the F&O / Dataverse data conversation, complementary to packaged movement. (Cross-referencing the related operational topic.)

Pixel notes

Build a package dashboard inside F&O — list of recent installs across environments, sortable by version. The dashboard is a model-driven page over the cdm_packageinstall table. One screen, one truth.

Forge notes

The DMF REST endpoint occasionally returns success for a job that partially failed. Always cross-check the executionId against the DMF “Job history” record’s ExecutionStatus field. If Succeeded is reported but ErrorCount > 0, the import partially failed and your code should treat it as a failure.

Key takeaways

  • Treat packages as build artifacts. Version them. Source-control the inputs.
  • Build the zip via a CI script that stamps the version into the manifest.
  • Import via the DMF REST endpoint with a service principal.
  • Audit installs into a custom table inside F&O.
  • Test against a fresh environment to catch entity-ordering bugs.
[object Object]
Share