SfSkills Domains Docs GitHub

Home / devops / second-generation-managed-packages

second-generation-managed-packages

2GP managed package development: creating managed packages with Dev Hub and Salesforce CLI, semantic versioning, patch versions, namespace linking, ISV AppExchange listing, and subscriber upgrade management. NOT for unlocked packages — use devops/unlocked-package-development. NOT for 1GP packaging orgs and namespace registration — use devops/managed-package-development.

Open the skill folder on GitHub


Second-Generation Managed Packages (2GP)

This skill activates when a practitioner needs to build, version, promote, or troubleshoot a second-generation managed package (2GP) for ISV distribution via AppExchange. It covers the full Dev Hub-centric workflow: namespace setup, package creation, version builds, patch releases, code coverage gating, and subscriber upgrade management.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

Dev Hub as Control Plane

In 2GP, the Dev Hub org is the single control plane for all managed packages, scratch orgs, and namespaces. Unlike 1GP, which required a dedicated packaging org per package and per patch version, 2GP eliminates these separate orgs. All sf package commands target the Dev Hub (via --target-dev-hub). Scratch orgs are ephemeral development environments created from the Dev Hub and are used for active development and CI — they are not packaging orgs.

The source of truth is version control, not an org. Package metadata lives in the Salesforce DX project directory and is committed to a VCS. sf package version create reads source from that project structure to produce a versioned artifact registered in the Dev Hub.

Semantic Versioning and Flexible Ancestors

2GP package versions follow a major.minor.patch.build numbering scheme, declared in sfdx-project.json under versionNumber. The .NEXT token increments the build number automatically on each version create run.

Unlike 1GP's strictly linear versioning, 2GP supports flexible versioning: if a managed-released version has not yet been distributed to any subscriber, it can be abandoned and a prior version selected as the ancestor for the next release. This is declared via ancestorVersion in sfdx-project.json. Once a version is installed by at least one subscriber, it is locked into the ancestor chain and cannot be abandoned.

Patch versions are created by specifying a non-zero patch number (e.g., 1.2.1). A patch version branches off an existing major.minor and allows bug fixes to be released without incrementing the minor version. In 2GP, creating a patch version requires no separate patch org — it uses the same CLI command with the appropriate version number.

Namespace Sharing and @namespaceAccessible

In 1GP, each managed package required a unique namespace, forcing ISVs to proliferate namespaces across their portfolio. 2GP allows multiple managed packages to share the same namespace. The @namespaceAccessible Apex annotation exposes public Apex classes and methods across packages within the same namespace, enabling modular ISV architectures without cross-namespace API surface exposure.

Dependencies and sfdx-project.json

Inter-package dependencies are declared declaratively in sfdx-project.json under the packageDirectories entry using a dependencies array. Each dependency references a package alias (mapped to a version ID in packageAliases). This replaces the manual tracking and org-based dependency management required in 1GP. Dependencies can include other 2GP packages (managed or unlocked) or 1GP managed packages.

{
  "packageDirectories": [
    {
      "path": "force-app",
      "package": "MyManagedApp",
      "versionName": "Spring Release",
      "versionNumber": "2.1.0.NEXT",
      "default": true,
      "dependencies": [
        {
          "package": "MySharedLibrary",
          "versionNumber": "1.3.0.LATEST"
        }
      ]
    }
  ],
  "namespace": "myns",
  "packageAliases": {
    "MyManagedApp": "0Ho...",
    "MySharedLibrary@1.3.0-1": "04t..."
  }
}

Common Patterns

Pattern 1: New 2GP Managed Package From Scratch

When to use: Starting a new ISV application with no existing 1GP package.

How it works:

  1. Enable Dev Hub and Second-Generation Managed Packages in the PBO.
  2. Register a namespace in a Developer Edition org. Link it to the Dev Hub via Namespace Registries > Link Namespace.
  3. Authenticate the CLI to the Dev Hub: sf org login web --alias devhub --set-default-dev-hub.
  4. Create the package: sf package create --name "My App" --package-type Managed --path force-app --target-dev-hub devhub.

    This registers the package in the Dev Hub and adds its 0Ho ID to sfdx-project.json under packageAliases.

  5. Develop metadata in a scratch org: sf org create scratch --definition-file config/project-scratch-def.json --target-dev-hub devhub --alias devorg.
  6. Push source and develop: sf project deploy start --target-org devorg.
  7. When ready to version: sf package version create --package "My App" --target-dev-hub devhub --code-coverage --wait 20.
  8. Test the beta version in a scratch org: sf package install --package 04t... --target-org devorg.
  9. Promote to released: sf package version promote --package 04t... --target-dev-hub devhub.

CLI push upgrades (Winter '26+). For subscriber orgs already on a released 2GP or unlocked package version, sf package install push (and related push-upgrade commands in the Salesforce CLI reference) can schedule a push upgrade instead of waiting for each subscriber to self-install — use for ISV broadcast fixes where the AppExchange upgrade path is too slow.

Why not the alternative: 1GP requires a dedicated packaging org per package and cannot be automated end-to-end through the CLI. 2GP integrates into CI/CD pipelines and eliminates org credential management overhead.

Pattern 2: Patch Version for a Released Minor

When to use: A bug is found in a shipped minor version (e.g., 2.1.0) and must be fixed without releasing a new minor (2.2.0) that would require re-qualifying the AppExchange listing.

How it works:

  1. Update versionNumber in sfdx-project.json to 2.1.1.NEXT and set ancestorVersion to 2.1.0.
  2. Apply only the bug fix to the source.
  3. Run: sf package version create --package "My App" --target-dev-hub devhub --code-coverage --wait 20.
  4. Test in a scratch org, then promote: sf package version promote --package 04t... --target-dev-hub devhub.
  5. Subscribers on 2.1.0 can upgrade to 2.1.1 directly.

Why not the alternative: Creating a new minor version (2.2.0) for a patch fix inflates the version history, may require AppExchange re-review, and forces subscribers onto a larger change than necessary.

Pattern 3: CI/CD Pipeline Integration

When to use: Automating package version builds for every merge to the main branch in a release pipeline.

How it works:

In the CI job:

# Authenticate using JWT (no interactive login)
sf org login jwt \
  --client-id $SF_CONSUMER_KEY \
  --jwt-key-file server.key \
  --username $SF_DEV_HUB_USER \
  --set-default-dev-hub

# Create version with code coverage, write result ID to file
sf package version create \
  --package "My App" \
  --target-dev-hub $SF_DEV_HUB_USER \
  --code-coverage \
  --wait 30 \
  --json > version-result.json

# Optionally promote if on a release branch
PACKAGE_VERSION_ID=$(jq -r '.result.subscriberPackageVersionId' version-result.json)
sf package version promote --package $PACKAGE_VERSION_ID --target-dev-hub $SF_DEV_HUB_USER

Why not the alternative: Manually creating versions is error-prone and does not catch regressions early. Automated promotion gating on coverage failures prevents non-promotable versions from reaching subscribers.


Decision Guidance

SituationRecommended ApproachReason
New ISV app, no prior 1GP investmentStart with 2GP from day oneAvoids future migration cost; full CLI/API automation available
Bug fix on released version with active subscribersCreate a patch version (e.g., 2.1.1)Minimizes subscriber disruption; no minor version inflation
Multiple ISV modules sharing the same namespaceUse @namespaceAccessible Apex across 2GP packagesAllows modular architecture without cross-namespace surface leakage
Inter-package dependencyDeclare in sfdx-project.json dependencies arrayDeclarative and VCS-traceable; eliminates manual org-based tracking
AppExchange submissionPromote version, then submit for security review from ISVforce partner portalPromotion gates code coverage; security review gates listing readiness
Dev Hub org considerationsUse Partner Business Org (PBO) as Dev HubAvoids package loss on org expiry; higher scratch org and version limits
Migrating from 1GPUse sf package convert + subscriber migration workflowConverts 1GP metadata to 2GP version; subscriber migration preserves data

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. Omitting --code-coverage makes a version unpromotable — If sf package version create is run without the --code-coverage flag, the resulting beta version can never be promoted to managed-released, even if coverage is 100%. The flag must be present at version creation time. A new version must be built with the flag to correct this.
  2. Dev Hub org association is permanent — Once a package is created against a Dev Hub org, that association cannot be changed or transferred. If a Developer Edition org used as a Dev Hub expires due to inactivity, all packages associated with it become orphaned: installed packages cannot receive updates and new installs may fail. ISVs must use their Partner Business Org as the Dev Hub.
  3. Subscriber orgs cannot see Apex source — Apex class, trigger, and Visualforce component source inside a managed 2GP package is obfuscated in subscriber orgs. Partners can view unobfuscated code only when logged in via the Subscriber Support Console. Attempting to reference or inspect source from a subscriber context fails silently — plan support and troubleshooting workflows around this constraint.

Output Artifacts

ArtifactDescription
sfdx-project.jsonProject configuration file containing package definitions, version numbers, namespace, and dependency aliases
Package (0Ho ID)The managed package container registered in the Dev Hub, referenced by all package versions
Package version (04t ID)A specific beta or promoted (managed-released) package version installable in subscriber orgs
Scratch org definition fileconfig/project-scratch-def.json defining features and settings required for development and testing
Patch version (04t ID)A promoted patch release branching from an existing minor version for targeted bug fixes

Package files