SfSkills Domains Docs GitHub

Home / devops / unlocked-package-development

unlocked-package-development

Use this skill when designing, creating, versioning, or installing unlocked packages: package directory configuration in sfdx-project.json, namespace management, package dependencies, version lifecycle (beta to released), and subscriber installation. NOT for 2GP managed packages (ISV namespaces, push upgrades, AppExchange distribution) — use devops/second-generation-managed-packages.

Open the skill folder on GitHub


Unlocked Package Development

This skill activates when you are designing or maintaining an unlocked package project: creating the package in a Dev Hub, defining package directories in sfdx-project.json, managing inter-package dependencies, creating and promoting package versions, and installing packages in subscriber orgs. It covers the full lifecycle from initial sf package create through subscriber installation — excluding 2GP managed packages (AppExchange ISV packaging) and 1GP managed packages.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

1. Package Identity: 0Ho vs. 04t IDs

Every unlocked package has two distinct ID types:

The package ID never changes. Version IDs are created per version and are immutable after creation. When referencing a package in a dependency list or install command, always use the 04t... subscriber package version ID, not the 0Ho... package ID.

2. sfdx-project.json — The Package Manifest

All package configuration lives in sfdx-project.json. The packageDirectories array defines which directories contain package source, and packageAliases maps human-readable names to Salesforce-assigned IDs.

Minimal valid package project:

{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "MyUnlockedPackage",
      "versionName": "Spring 2025",
      "versionNumber": "1.0.0.NEXT",
      "dependencies": []
    }
  ],
  "namespace": "",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "63.0",
  "packageAliases": {
    "MyUnlockedPackage": "0Ho..."
  }
}

Key fields in packageDirectories:

FieldRequiredNotes
pathYesRelative path to the package source directory
packageYesHuman-readable package name; must match a key in packageAliases
versionNumberYesSemantic version in major.minor.patch.build format; use NEXT for auto-increment
versionNameNoDisplay name for this version
dependenciesNoArray of { "package": "<alias>", "versionNumber": "..." } objects
ancestorIdNoPackage version ID (04t...) of the version this version descends from
definitionFileNoPath to scratch org definition file used during version creation

3. Package Version Lifecycle: Beta vs. Released

Every version created with sf package version create starts as beta. Beta versions:

Promotion to Released (sf package version promote) is a one-way, irreversible operation that:

Released versions can be installed in any org type. Plan promotion as a deliberate gate before production deployments.

4. Package Dependencies

If Package B depends on Package A, the dependency must be declared in Package B's packageDirectories entry and Package A must already be installed in the org where Package B is being created or tested.

Dependencies are specified by alias and minimum version number in sfdx-project.json:

"dependencies": [
  {
    "package": "SharedComponents",
    "versionNumber": "2.3.0.LATEST"
  }
]

The alias (SharedComponents) must resolve to a subscriber package version ID (04t...) in packageAliases. Use LATEST in the version number to resolve to the latest released version of a dependency, or pin to an exact version for reproducible builds.

During sf package version create, Salesforce validates that all declared dependencies are installed in the scratch org or build org used for version creation. Missing dependencies cause a PACKAGE2_VERSION_CREATE_DEPENDENCY_UNRESOLVED error.

5. Namespace Configuration

The namespace field in sfdx-project.json is set once per project:

The namespace declaration in sfdx-project.json must match the namespace registered in the Dev Hub. A mismatch causes package creation to fail.


Common Patterns

Mode 1 — Create a Package and Publish a First Version

When to use: Greenfield package project, or setting up an existing repo to use unlocked packages for the first time.

# Step 1: Create the package in Dev Hub (run once per package)
sf package create \
  --name "MyUnlockedPackage" \
  --package-type Unlocked \
  --path force-app \
  --target-dev-hub MyDevHub

# This outputs a Package ID (0Ho...) — copy it into sfdx-project.json packageAliases

# Step 2: Create a scratch org for development
sf org create scratch \
  --definition-file config/project-scratch-def.json \
  --alias pkg-dev --duration-days 7 \
  --target-dev-hub MyDevHub

# Step 3: Push source to scratch org (iterative development)
sf project deploy start --target-org pkg-dev

# Step 4: Create a beta package version
sf package version create \
  --package MyUnlockedPackage \
  --installation-key-bypass \
  --wait 10 \
  --target-dev-hub MyDevHub

# This outputs a Package Version ID (04t...) — copy into packageAliases as a versioned alias

# Step 5: Install beta version in a sandbox for QA
sf package install \
  --package 04t... \
  --target-org QASandbox \
  --wait 10

# Step 6: Promote to Released before production install
sf package version promote \
  --package 04t... \
  --target-dev-hub MyDevHub

Mode 2 — Review and Audit Package Version Coverage and Status

When to use: Pre-promotion gate check, CI pipeline validation, or troubleshooting a version that is failing to promote due to coverage or dependency issues.

# List all versions of a package
sf package version list \
  --package MyUnlockedPackage \
  --target-dev-hub MyDevHub \
  --verbose

# Get detailed info about a specific version
sf package version report \
  --package 04t... \
  --target-dev-hub MyDevHub

# Check code coverage for a version
sf package version report \
  --package 04t... \
  --target-dev-hub MyDevHub \
  --json | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['result']['CodeCoverage'])"

# List all packages registered to the Dev Hub
sf package list --target-dev-hub MyDevHub

Coverage is reported per class in the version report output. Classes below 75% are listed individually, helping identify exactly which Apex needs more tests before promotion is possible.

Mode 3 — Troubleshoot Installation and Dependency Failures

When to use: sf package install fails, or version create reports unresolved dependencies.

Common install error: PACKAGE_UNAVAILABLE

The package version does not exist in the target org's region, or the version ID is a beta being installed into production. Verify the version is Released before installing to production.

Common error: DUPLICATE_COMPONENT

A component with the same API name already exists in the target org (from a deployment or another package). Unlocked packages do not automatically take ownership of pre-existing components. Resolution options:

  1. Remove the conflicting component from the org before installing.
  2. Restructure the package to avoid the conflicting component name.

Common error: dependency not installed

# Check what packages are installed in the target org
sf package installed list --target-org TargetOrg

# Install missing dependency first
sf package install --package 04t_DEPENDENCY_VERSION_ID --target-org TargetOrg --wait 10

# Then retry the main package install
sf package install --package 04t_MAIN_PACKAGE_ID --target-org TargetOrg --wait 10

Common error during version create: PACKAGE2_VERSION_CREATE_DEPENDENCY_UNRESOLVED

Declare dependencies in sfdx-project.json and ensure they are installed in the scratch org used for version creation. The scratch org definition file (definitionFile in the package directory) can reference a snapshot of installed packages.


Decision Guidance

SituationRecommended ApproachReason
Internal enterprise app, single teamNamespace-less unlocked packageSimpler API names, no namespace registration overhead
Sharing package across teams or orgsNamespace-less with installation keyKey restricts install to authorized teams without full namespace setup
ISV or AppExchange distributionUse 2GP managed package (outside this skill's scope)Managed packages support push upgrades, namespace locking, and subscriber org protection
Multiple interdependent packagesMap dependency order first; create and version in dependency orderInstalling out of order causes DEPENDENCY_UNRESOLVED errors at both version create and install time
CI pipeline version creationPin dependency versions to LATEST or an exact 04t...Avoids non-deterministic resolution failures when upstream packages change
Production deploymentPromote version before pipeline reaches prodBeta versions are blocked from production installs by the platform
Need to roll back an installed packageUninstall and reinstall prior versionUnlocked package components can be deleted on uninstall; verify subscriber org impact before rolling back
Code coverage blocking promotionRun sf apex run test in the version creation org and address failing or uncovered classesCoverage must reach 75% across all Apex in the package directory, not org-wide

Step-by-step instructions for an AI agent or practitioner activating this skill:

  1. Gather context — confirm the org edition, relevant objects, and current configuration state
  2. Review official sources — check the references in this skill's well-architected.md before making changes
  3. Implement or advise — apply the patterns from Core Concepts and Common Patterns sections above
  4. Validate — run the skill's checker script and verify against the Review Checklist below
  5. Document — record any deviations from standard patterns and update the template if needed

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

  1. Namespace-less packages are permanent — Once a package is created without a namespace (empty namespace field), it cannot be converted to a namespaced package. If the team later wants namespace isolation, a new package must be created and all subscribers must reinstall.
  2. Package version ancestry must be linear — branching version numbers is not supported — The ancestorId field enforces a single linear version chain. You cannot create version 2.0 branching from both 1.0 and 1.5. If your team runs parallel feature branches that need separate package versions, you must manage this through scratch org isolation, not separate version lineages.
  3. Deleting an unlocked package uninstalls and destroys its components in subscriber orgs — Unlike change sets or Metadata API deployments, unlocked package components are owned by the package. Uninstalling an unlocked package removes its components from the subscriber org (unless --no-prompt is used and the org has dependent components, in which case it fails). Subscribers must be warned before an uninstall.
  4. Beta versions cannot be installed in production — not even with overrides — The platform enforces this at install time. There is no flag or permission that bypasses it. Teams that run sf package install in a CI deploy step targeting production must always promote first.
  5. Code coverage is evaluated at version create time using the scratch org, not the subscriber org — Coverage is measured against the Apex in the package directory during sf package version create. If your scratch org definition file does not load the same test data or settings as production, coverage numbers may differ. A version can pass promotion threshold in scratch org but the same tests may have different coverage behavior in a real org.

Output Artifacts

ArtifactDescription
sfdx-project.jsonPackage manifest with packageDirectories, packageAliases, namespace, and dependency configuration
Package ID (0Ho...)Permanent identifier for the package container in Dev Hub; stored in packageAliases
Package Version ID (04t...)Immutable snapshot ID for a specific build; used in sf package install and CI pipeline references
sf package version list outputAudit view of all versions: version numbers, status (beta/released), created date, code coverage
Install commandsf package install --package 04t... --target-org <org> --wait 10 with optional --installation-key

Package files