Source Packages

A source package is a directory of metadata that sfp versions, builds into an artifact, and deploys to an org through the Metadata API as a unit. It is the default package type in a flxbl project — see Choosing a package type for when to choose another.

Unlike an unlocked package, a source package has no Salesforce-side package version or component lock. sfp supplies the module's version, lifecycle, and deployment behaviour instead of the platform.

When to use a source package

Source packages are the default, and they are the only option for some metadata:

  • Metadata the Metadata API supports but unlocked packaging does not — see the Metadata Coverage Report.
  • Org-specific and environment-specific metadata — queues, profiles, permission sets, custom settings, and components that vary across environments.
  • Configuration layered on managed packages — help text, field descriptions, and similar changes to components a managed package delivers.
  • Metadata that needs destructive changes — components removed through pre- and post-destructive entries.
  • Environment-specific variants of a component — deploy a different variant per org with aliasified packages (a source-package-only capability, with an optional merge mode that shares common content), or swap configuration values at deploy with text replacements.

What a source package provides

The properties that once required converting a source package to an unlocked package are provided by sfp and codev:

PropertyMechanism
A versioned, immutable artifactsfp builds the package into a versioned artifact, through the same build path as any other type.
Deploys only the components that changedComponent checksum skip — on by default — fetches the previously installed artifact and deploys only the components whose source bytes differ.
Runs only the tests the change touchesWhen checksum skip reduces the deploy, sfp sets the test level to RunRelevantTests.
Detecting when an org diverged from sourcecodev environment drift detection compares the source it expects on an environment against what the org contains.
Reconciling an org back to sourceSync redeploys branch state to an environment; a changeset captures an org-side change and raises it as a pull request.
Removing components that left the modulePre- and post-destructive entries.

Tradeoffs against unlocked packages

A source package does not carry a Salesforce-managed package version, so:

  • No platform component lock. The org does not know package boundaries; another package can overwrite a component. Ownership rests on your repository structure, and divergence is caught by drift detection rather than prevented by the platform.
  • Dependencies are validated at deploy time, against the target org, not when the package is built.

These are the reasons to choose an unlocked package for a given module — component ownership across orgs and build-time dependency validation. Choosing a package type covers the decision.

Components commonly overwritten across packages

Without a platform lock, keep a single owner for components that several packages tend to touch:

  • Custom Labels
  • Profiles and Permission Sets
  • Custom Settings
  • Global Value Sets

Environment-specific configuration

Source packages support two mechanisms for environment-specific differences.

Aliasified packages

Aliasified packages are a source-package-only capability: at install, sfp deploys the subfolder whose name matches the target org's alias, and falls back to a default subfolder when no alias matches.

{
  "path": "src/src-env-specific",
  "package": "src-env-specific",
  "versionNumber": "2.0.10.NEXT",
  "aliasfy": true
}
src-env-specific/
├── default/          # fallback; deployed to sandboxes only
│   └── permissionsets/
├── dev/              # deployed to an org aliased "dev"
│   └── permissionsets/
└── prod/             # deployed to an org aliased "prod"
    └── permissionsets/

Merge mode adds content inheritance. With "aliasfy": { "mergeMode": true }, sfp merges the default folder's content into each alias subfolder at build time — an alias subfolder overrides only the files it redefines — so shared metadata is not duplicated across folders. In merge mode the default folder deploys to any environment, including production, and the package supports push and pull against the default subfolder.

{
  "path": "src/src-env-specific",
  "package": "src-env-specific",
  "versionNumber": "2.0.10.NEXT",
  "aliasfy": { "mergeMode": true }
}

See Aliasfy packages and merge mode for details.

Text replacements

Replace configuration values during deployment without file duplication:

# preDeploy/replacements.yml
replacements:
  - name: "API Endpoint"
    glob: "**/*.cls"
    pattern: "%%API_URL%%"
    environments:
      default: "https://api.dev.example.com"
      prod: "https://api.example.com"

See String Replacements for details.

Destructive changes

Source packages remove components through dedicated folders:

  • pre-destructive/: components deleted before deployment
  • post-destructive/: components deleted after deployment
my-package/
├── main/
│   └── default/
├── pre-destructive/     # Deleted before main deployment
│   └── objects/
└── post-destructive/    # Deleted after main deployment
    └── classes/

Destructive changes are detected and processed during package installation.

Apex test execution

  • Sandbox / scratch org: tests are skipped by default for faster iteration.
  • Production: tests always run and coverage is validated (a Salesforce requirement).
  • Overrides:
    • sfp install --runtests forces test execution when installing to a sandbox.
    • Package-level skipTesting in sfdx-project.json (ignored in production, where every class deployed needs 75% coverage or more).

When component checksum skip reduces the deploy, sfp narrows the test level to RunRelevantTests, so only the tests the deployed Apex touches run.

Dependency management

A source package can depend on other source, unlocked, or managed packages. Dependencies are validated at deploy time — the dependent metadata must already exist in the target org.

For development in scratch orgs, declare dependencies so sfp installs them first:

{
  "packageDirectories": [
    {
      "path": "src/my-package",
      "package": "my-package",
      "versionNumber": "1.0.0.NEXT",
      "dependencies": [
        {"package": "another-source-package"},
        {"package": "unlocked-package@1.2.3"},
        {"subscriberPackageVersionId": "04t..."}
      ]
    }
  ]
}

sfp prepare and sfp validate install declared dependencies before deploying the source package.

Starting from unpackaged metadata

  1. Identify logical groupings of metadata by domain.
  2. Create source package entries in sfdx-project.json.
  3. Move metadata into the package directories, keeping a single owner per component.
  4. Declare dependencies between packages.
  5. Deploy to a scratch org or sandbox to confirm order.

Converting to an unlocked package

Converting is a deliberate choice for a specific module, not a step every package takes. Convert a module when you want platform-enforced component ownership or build-time dependency validation:

  1. Confirm the module's components are stable.
  2. Create the package in your DevHub and add its alias to sfdx-project.json.
  3. Keep environment-specific and org-specific metadata as source packages.

The module boundary and its version history are unchanged; only how the module builds and deploys changes. See Choosing a package type.

On this page