I'm new to Salesforce package development, and my team is currently following a package development model that involves both Unlocked and Managed packages. We're facing some challenges with setting up our packages and are seeking advice on best practices.

Here's our current approach:

We have two types of Salesforce orgs in our workflow: development org (Developer Edition) and production org (Enterprise Edition).

  • In our development orgs, we create Unlocked packages to facilitate development and testing.

  • In the production org, we create Managed packages for the final deployment.

Our problem arises when we need to create packages with the same path but of different types. For instance:

  "packageDirectories": [
      "path": "packages/user",
      "package": "Unlocked Stag Usermgmt",
      "versionName": "ver 0.1",
      "versionNumber": "0.1.0.NEXT",
      "versionDescription": "",
      "default": true
      "path": "packages/user",
      "package": "Managed Usermgmt",
      "versionName": "ver 0.1",
      "versionNumber": "0.1.0.NEXT",
      "versionDescription": "",
      "default": false
  "name": "erp",
  "namespace": "",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "58.0",
  "packageAliases": {
    "Unlocked Stag Usermgmt": "<id>",

The duplication of the packages/user path is causing an error in sdfx-project.json.

The reasons for creating two types of packages are:

  • We want to avoid potential unintended customizations when installing Unlocked packages in production orgs.
  • Installing Managed packages in development orgs (stag and scratch orgs) might limit our development flow.

We're looking for advice on best practices to manage this situation efficiently and ensure a smooth development and deployment process. Any guidance on structuring the project, handling paths, or alternative approaches would be greatly appreciated.

Should we use only one Unlocked or Managed Packages instead both?

Thank you for your assistance!

  • Have you considered using managed betas instead of unlocked?
    – Phil W
    Oct 20, 2023 at 12:32
  • @PhilW Honestly, I'm new. so It'd be great if you can share me some insights about different between them. I did search on my own. but not really understand what is to differ between them.
    – vctqs1
    Oct 20, 2023 at 13:30
  • Im trying to find the best practice in community
    – vctqs1
    Oct 20, 2023 at 13:31

1 Answer 1



The two package types have different intended use cases. You'll know which one to use by asking yourself one simple question: "Am I packaging for internal processes or for a distributed app (e.g. AppExchange)?" If it's internal use only, use an Unlocked Package. If it's intended for AppExchange or other distribution channels, it's a Managed Package, even if you intend to use it internally as well.

The new model for managing metadata is that the "source of truth" has changed from a single org to a single repository. This makes it easy to keep orgs (production, sandboxes, etc) in sync. The "unintended customization" quality of Unlocked Packages is not a bug, it's a feature. Don't use Managed Packages unless you intend to make a distributed app, and don't try to mix the types, as that is not the intended design.

You should not be building two different packages. The software development lifecycle (SDLC) is identical for both types of packages, so the only reason to choose one over the other is for the intended consumers. Unlocked Packages (2UP) are for subscribers to manage their own internal metadata, and Second-Generation Managed Packages (2GP) are for ISVs to develop and deploy apps on the AppExchange or by other means for subscribers. The main differences between the two are that 2UP do not offer Intellectual Property (IP) protection and cannot be listed on the AppExchange, similar to a First Generation Unmanaged Package (1UP), while 2GP packages restrict certain kinds of component deletion that 2UP doesn't care about, similar to a First Generation Managed Package (1GP).

It appears you're under the impression that 2GP development involves installing the package. This is not true. The Software Development Lifecycle (SDLC) is identical for both types of packages. You're just missing some configuration. First, if you haven't already, enable Dev Hub in your production org. This is your subscriber org if you're a client, your Partner Business Org (PBO) if you're an ISV/OEM, or a Developer Edition org without a registered namespace for other purposes (e.g. hobby development, independent consultant, etc). The latter type of org has very limited resources compared to a paid org, though. We'll refer to this newly configured org as the Dev Hub Org (DHO). You cannot use any existing First Generation Managed Package (1GP) Developer Edition org as a Dev Hub, though you can use this org for the namespace it carries. Also make sure you've enabled the appropriate permissions for your developers to use the packaging features.

Next, link the namespace in the DHO. Go to the Namespace Registries tab in the org, and click on Link Namespace. This opens a window for you to log in to the Developer Edition that holds the namespace for your packages. Note that one namespace can be shared across multiple DHOs, and multiple namespaces can be linked to a DHO. Further, multiple packages created in the same namespace can be installed in a single org if there are no naming conflicts. Once you've done this, you can use the namespace property in your sfdx-project.json configuration file to create packages that belong to that namespace and, perhaps more importantly, create scratch orgs that have that namespace. As you're probably aware, multiple Developer Edition (DE) orgs can't share a namespace. Scratch Orgs can share a namespace, which enables them to act like a namespace-bearing DE org; development in this kind of org closely mirrors how development in 1GP used to work, but without all the conflicts and overwrites possibilities by multiple developers working in a single org.

The main difference is that instead of having a permanent org you use to develop in, and can potentially break by accidental metadata deletion, Scratch Orgs with a namespace are easily deleted without any consequence to the package. It becomes much harder to accidentally break your Packaging Org. And with 2UP and 2GP, you can even create alternative branches as a kind of ultimate do-over. However, upgrades are allowed only between versions with a common ancestor. See the documentation about ancestry.

So, what does the SDLC look like for 2GP and 2UP? Once you've set up the appropriate configuration in your DHO, the process is fairly straightforward. First, create a Scratch Org. Then, use sf force source push to deploy the metadata to the org. After that, create a new branch in the repo, as you would in any standard SDLC setup. Any changes you make in the Scratch Org can be retrieved with sf force source pull. When you're satisfied with your changes, use sf package version create to create a new version, and optionally promote it with sf package version promote. This promotion allows installation to production orgs. Note that this is different from 1GP, where you had to choose to "promote" ("Release") at the time of upload. After everything is done, you finalize with the appropriate commits and merges in the repo. The Scratch Org can then be deleted, and developers can work on new features/bugs.

Note that, unlike 1GP "Beta" versions, a 2GP version can be promoted at any time, and once installed, can be upgraded to later versions regardless if they are promoted or not (assuming the org allows unpromoted versions). Also notice that no two developers will ever conflict metadata in their respective Scratch Orgs, which is another major benefit over 1GP. Any conflicts that happen will necessarily occur in the repo itself, which is far more manageable.

As far as "unintended customizations" that 2UP may cause, that's an intentional design choice. Before 2UP, admins and developers alike could cause all kinds of unintentional customizations without any warning. In the new model, all changes should come from the repository, which is the source of truth. Unintended customization is now anything done in a production org to bypass the process. While it does take more time, it provides better safety in case of various "oops" moments that tend to happen from time to time. You should see 2UP not as a threat to your org's metadata, but rather as a way to protect it from accidental changes that originate outside the prescribed process.

  • Hi, thank you for the very detailed answers. I used the commands sf project retrieve start and sf project deploy start. However, my project has multiple packages. When I install using the above commands, it can't retrieve profiles inside the packages, which leads to missing profiles in scratch orgs. How can I deal with this issue
    – vctqs1
    Oct 23, 2023 at 10:45
  • Also, I didnt do any custom Profile, just admin system only. But when using sf project retrieve start and sf project deploy start in scratch org, No packages visible because missing permission. That why I have to change to sf package install --security-type AdminOnly
    – vctqs1
    Oct 23, 2023 at 10:51
  • @ThuVo Installs should be done in scratch orgs before deploying the source code.
    – sfdcfox
    Oct 23, 2023 at 14:27
  • I got confuse, What u mean installs here is installing package or install anything else? Could you help give some specific details, please? Ohm in my context, I'd like to set up workflow in local for whole team
    – vctqs1
    Oct 23, 2023 at 15:30
  • @ThuVo Install any dependent (e.g. AppExchange) apps before attempting to deploying your own package's code.
    – sfdcfox
    Oct 23, 2023 at 18:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .