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:
| Property | Mechanism |
|---|---|
| A versioned, immutable artifact | sfp builds the package into a versioned artifact, through the same build path as any other type. |
| Deploys only the components that changed | Component 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 touches | When checksum skip reduces the deploy, sfp sets the test level to RunRelevantTests. |
| Detecting when an org diverged from source | codev environment drift detection compares the source it expects on an environment against what the org contains. |
| Reconciling an org back to source | Sync 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 module | Pre- 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 --runtestsforces test execution when installing to a sandbox.- Package-level
skipTestinginsfdx-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
- Identify logical groupings of metadata by domain.
- Create source package entries in
sfdx-project.json. - Move metadata into the package directories, keeping a single owner per component.
- Declare dependencies between packages.
- 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:
- Confirm the module's components are stable.
- Create the package in your DevHub and add its alias to
sfdx-project.json. - 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.