Thanks for the question! I've created an answer in three parts, each one meant to address a specific question you've asked.
- PART ONE: How to Create Second-Generation Extension Packages
- PART TWO: How to Get the LMA to "see" a Second-Generation Managed Package
- PART THREE: Technical Enablement for Second-Generation Extension Packages
Not gonna lie, this is a long read. For those who want something shorter, here's the TL;DR.
Second-generation extension packages are created by adding dependencies on metadata found in other packages to a second-generation package, then creating a new package version using force:package:version:create
.
The LMA has no problem seeing 2GP extension packages so long as your Dev Hub is linked to your publishing console and you've created and promoted at least one second-generation package version from that Dev Hub.
There are several enablement materials that are relevant to this topic, but they're spread out over a large number of sites and locations. If you have just 17 minutes to learn more, the one resource to check out is this video from DreamTX '20: Build Apps Using 2nd-Generation Managed Packages.
For those with time to kill (and an itch to learn!) let's dive in!
PART ONE: How to Create Second-Generation Extension Packages
Fundamentally, an "extension package" is any package that has one or more hard dependencies on another package. For example, let's say that Package A
and Package B
start life out as unrelated entities. The moment that Package B
adds a custom field, Apex reference, or other hard dependencies to metadata components that are defined in Package A
...that moment is when Package B
becomes an "extension" of Package A
.
How Package Dependency Works During Development
Package dependency is at the heart of what makes one package an "extension" of another, so it's important to understand the rules that govern this interaction, especially those that impact you during development.
Package Dependency Rule #1: Package dependencies (i.e. "base packages") must be present in the org where development of the dependent (i.e. "extension") package takes place in order to satisfy design-time metadata dependencies.
- For 1GP, this means that the package dependency must be installed in the packaging org
- For 2GP, this means that one of two things must happen
- If the package dependency has a different namespace, it must be installed in any namespaced scratch orgs where development takes place
- If the package dependency has the same namespace, it could either...
- Be installed in any namespaced scratch orgs where development takes place
- Be present in its own package directory within the same SFDX Project as the extension package source is, allowing the source of both packages to be deployed to the scratch org using
force:source:push
Package Dependency Rule #2: 2GPs that depend on other packages must explicitly define such dependencies inside of the sfdx-project.json
file at the root of the SFDX project directory where the 2GP is being developed.
Package Dependency Rule #3: Second-Generation Packages (2GPs) can depend on both First-Generation Packages (1GPs) and other 2GPs. On the other hand, 1GPs can only depend on other 1GPs because extending a 2GP with a 1GP is not supported by Salesforce. In other words...
- 1GP extending 1GP >>> OK
- 2GP extending 1GP >>> OK
- 1GP extending 2GP >>> NOT OK
- 2GP extending 2GP >>> OK
Package Dependency Rule #4: Circular package dependencies are prohibited. This means that if Package B
depends on Package A
then it's not OK for Package A
to also depend on Package B
. Packages built in this way would never be installable because no matter which package you install first the subscriber org would always be missing a package dependency.
Package Dependency in Action
With all the rules of package dependency in mind, let's look at an example of 2GP extension package development in action. Consider this excerpt of a Salesforce DX project definition file, sfdx-project.json
, used during the development of an expense calculator app.
// Note: This is an excerpt of an sfdx-project.json file, used for demonstration
// purposes only. A complete file would have additional keys defined.
{
"namespace": "exp_calc_demo",
"packageDirectories": [
{
"path": "accounting-logic",
"default": false,
"package": "Accounting Logic",
"versionName": "Summer ‘21",
"versionNumber": "4.5.0.NEXT",
"dependencies": [
{
"package": "Apex [email protected]"
}
]
},
{
"path": "expense-calculator",
"default": true,
"package": "Expense Calculator",
"versionName": "Summer ‘21",
"versionNumber": "1.2.0.NEXT",
"dependencies": [
{
"package": "Apex [email protected]"
},
{
"package": "Accounting Logic",
"versionNumber": "4.5.0.LATEST"
}
]
}
],
"packageAliases": {
"Expense Calculator": "0HoB00000002JAiWAP",
"Accounting Logic": "0HoB00000004CFpKAM",
"Apex [email protected]": "04tB0000000IB1EIYR"
}
}
Observation #1: The "Expense Calculator" app is actually an MPSN (multi-package, same-namespace) solution because both the Expense Calculator
and Accounting Logic
packages are 2GPs that share the same namespace. We know this because each package is defined inside of the packageDirectories
object array and each one has a package alias that points to a 0Ho
Package2 ID.
Observation #2: The definition of the Accounting Logic
package directory appears before the definition of the Expense Calculator
package directory. When running the force:source:push
command, this causes the Salesforce CLI to push the project source one package directory at a time in the same order as those package directories are defined inside sfdx-project.json
This is very important because the Expense Calculator
package has metadata components that depend on the metadata found in the Accounting Logic
package. When getting an empty scratch org ready for development, if the source from the Accounting Logic
package was not pushed to the scratch org first the force:source:push
command would end up failing due to invalid metadata references.
Observation #3: The dependency between Expense Calculator
and Accounting Logic
is an internal dependency because both packages share the same namespace and were created against the same Dev Hub. This allows the Expense Calculator
package directory definition to use the specialized syntax of "versionNumber": "4.5.0.LATEST"
to specify which version of the Accounting Logic
package it depends on.
Observation #4: The dependency on Apex [email protected]
is an external dependency because that package is neither part of the same namespace nor owned by the same Dev Hub as the Expense Calculator
and Accounting Logic
packages.
The fact that it's an external dependency is also why Apex [email protected]
is aliased to a 04t
package version ID. Note that both Expense Calculator
and Accounting Logic
are aliased to 0Ho
Package2 IDs, something that's only possible when the packages referenced are owned by the same Dev Hub that's being used to run any force:package
commands in this project.
Observation #5: It's not clear from looking at sfdx-project.json
so I'll have to state it here: The Expense Calculator
package in our example doesn't actually have any direct dependencies on the Apex [email protected]
package.
If that's the case, why do we need to explicitly list Apex [email protected]
as a dependency of Expense Calculator
?
The reason is that Second-generation packages must declare all package dependencies, even indirect ones. When you run force:package:version:create
the 2GP Build System needs to know the complete list of packages to install in the behind-the-scenes Build Org where your package is validated.
When the build org is created, each package dependency defined by the package being built will be installed in the same order in which they are defined. That's why Apex [email protected]
is the first dependency listed for the Expense Calculator
package. It must be installed first because the package that Expense Calculator
actually depends on, Accounting Library
, would fail on installation in the Build Org if Apex [email protected]
wasn't already there.
Final Observations: There is a lot for a developer to keep track of when it comes to package dependencies in 2GP. Knowing the responsibilities upfront can help avoid problems.
- Developers must identify the specific package versions that each 2GP depends on, whether those dependencies are direct or indirect
- External package dependencies must be installed in scratch orgs before the source of 2GPs that depend on those packages can be pushed/deployed to the scratch org
- Very little is automated (yet), so developers must be ready to manually determine indirect dependencies and install package dependencies into scratch orgs during development
PART TWO: How to Get the LMA to "see" a Second-Generation Managed Package
Connecting a second-generation managed package to an LMA is a relatively straightforward, two-step process...once you know how to do it. To make the first time easier, I'll walk through the process visually.
STEP ONE: Connect the Publishing Console to Your Dev Hub
This step assumes that your company has already joined the partner community and the Salesforce AppExchange Partner Program. If you haven't done this yet, please see the Connect with the Salesforce Partner Community unit on Trailhead for details on how to proceed.
- Log in to the Partner Community with a user who's part of your partner organization and has been granted the Listings permission
- Click on the Publishing tab to open the Publishing Console
- Click on the Organizations sub-tab
- Click the Connect Org button to start the Connection Wizard
- Enter the username of an admin user from the org where your Developer Hub lives
- Enter this user's password with the security token appended
- If you don't have or don't know the security token for this user, you'll have to reset it
- Click the Connect button to complete the process
STEP TWO: Register Your Second-Generation Package with Your LMA
Please note that it may take several minutes for the packages owned by your linked Dev Hub to show up in the Publishing Console. Also note that ONLY released (i.e., non-beta) packages will show up here and that it may take several minutes for newly promoted 2GPs to appear.
- Log in to the Partner Community with a user who's part of your partner organization and has been granted the Listings permission
- Click on the Publishing tab to open the Publishing Console
- Click on the Packages sub-tab. This will show you...
- All released (i.e. non-beta) first-generation managed packages owned by the 1GP packaging orgs that you've linked to your Publishing Console
- All first-generation unmanaged packages owned by orgs (packaging or otherwise) that you've linked to your Publishing Console
- All released (i.e. non-beta) second-generation managed packages owned by each Dev Hub that you've linked to your Publishing Console
- Observe how different packages,
VMC 2GP Lab (core-ux)
and VMC 2GP Lab (SR-Test)
, each with the same namespace, vmc2gplab
, are both listed here.
- This is what happens when you link a Dev Hub to the Publishing Console
- All second-generation packages owned by the linked Dev Hub which have at least one released (i.e. non-beta) package version will show up here.
- Connecting a managed package to your LMA requires you to click the Register Package link next to the package version that you want to wire up to your LMA. Clicking this link will kick off the package registration wizard.
- Click the Log In button to go to the next stage of the package registration wizard.
- Enter the username of an admin user from your License Management Org (LMO)
- "License Management Org" or "LMO" is a term used to describe an org where the License Management App (LMA) is currently installed.
- Enter this user's password with the security token appended
- If you don't have or don't know the security token for this user, you'll have to reset it.
- Click the Log In button to move to the next step in the package registration wizard.
- Choose the default license behavior.
- Trial licenses give subscribers access to your solution for up to 90 days.
- Active licenses provide subscribers with continuous access.
- Set the length of trial licenses to some number of days.
- The maximum value that can be set is 90 days.
- When the default license is *active instead of trial, the option to specify the length of trial disappears.
- Choose whether the default license should be site-wide (available for all users) or per-seat (must be granted to users individually).
- If selecting per-seat as your default license, you must also specify the number of seats that will be granted to the subscriber by default.
- Click the Save button when done to complete the package registration wizard.
Key Considerations When Using the LMA with Second-Generation Packages
Please keep these things in mind when using the LMA with first and second-generation packages
- Packages will only appear in your publishing console once you've...
- Connected a packaging org (1GP) or Dev Hub (2GP) to your Publishing Console
- Uploaded a managed/released package version (1GP) or promoted a beta package version to the released state (2GP)
- Packages do not have to pass security review before being registered with your LMA
- Partners are welcome to register packages with their LMA early
- This allows partners to get comfortable with being "hands-on" with the LMA
- Registering your package with the LMA also makes it possible to see how the Feature Management App (FMA) works end-to-end
- IMPORTANT! Being able to register a package with your LMA before passing Security Review is not an invitation to skip Security Review or the AppExchange Listing/Contracting process and distribute your package directly to customers. Doing so is a violation of the Salesforce Developer Services MSA and the Salesforce Partner Program Agreement (SPAA).
- Installing in sandbox and trial orgs is fine.
- Installing in production orgs is not. Please don't do it!
- To go through Security Review, packages must be associated with a Listing.
- This is a core part of how the AppExchange publishing system works and can not be bypassed.
- Having a listing does not mean your app has to be publicly listed (i.e. visible) on the AppExchange.
- Creating a listing, going through Security Review, and keeping your listing private so customers must install via URL or
04t
package version ID is perfectly acceptable.
PART THREE: Technical Enablement for Second-Generation Extension Packages
Documentation and enablement material for packaging, in general, can be found in many places. The Salesforce ISV Platform Expert Team aggregates 2GP specific enablement content in this Trailmix:
Tech Enablement: Second-Generation Packaging (2GP)
As for enablement resources that are relevant to the topic of 2GP extension packages, Phil W shared some great links in his answer. I'll share them again here along with some other ones I know of. (Thanks, Phil!)
Finally, there's this super long article-disguised-as-StackExchange-answer which is (hopefully) a nice enablement resource all by itself. :-)