To create a managed package in Salesforce today, you build a second-generation (2GP) package. Make your Partner Business Org the Dev Hub and link a namespace from a Developer Edition org. Then create the package with Salesforce CLI, build a version with code coverage, and promote it to released. The first version is the easy part. Every version after it has to follow rules about ancestors, locked API names and global Apex, and those rules decide whether your customers can upgrade at all.
For a SaaS company, the package is the product your customers install in their own orgs, and you will ship new versions of it for as long as you sell it. That makes the early setup choices (which org owns the packages, which namespace, what goes global) business decisions as much as engineering ones. Appnigma builds these apps for SaaS teams. Its marketplace apps work delivers "a Salesforce managed package or HubSpot app built around your product and customer workflows," built by ex-Salesforce engineers. If you are still deciding whether to ship a managed package at all, start with what a managed package is and how it differs from unlocked and unmanaged.
Updated October 2026. Checked against Salesforce's Second-Generation Managed Packaging Developer Guide, the Salesforce CLI reference and the Apex Developer Guide for API version 68.0 (Winter '27). The earlier version recommended WITH SECURITY_ENFORCED, which Apex rejects from API version 67.0. Its version command left out code coverage, so the version it built could never be promoted. It also said you install the License Management App yourself, and it described a downgrade version that doesn't exist. This update adds package ancestors, the promotion blockers, what locks after release, and the Apex behavior that changes at API version 67.0.
The setup choices you can't undo later
Three choices are made once and stay with the package for its whole life, so settle them before anyone writes code.
The Dev Hub is the org that owns your packages. Salesforce's Enable Dev Hub page says "All Salesforce ISV and OEM partners should designate their Partner Business Org (PBO) as their Dev Hub org." The same page warns that "after you enable Dev Hub, you can't disable it," and that when a trial or Developer Edition Dev Hub expires, "you lose access to all packages associated with that Dev Hub org." That second line is why a package started in an engineer's personal trial org becomes a migration project later. Our guide to enabling Dev Hub in your Partner Business Org covers the PBO side.
The namespace is the prefix on every component's API name. You create it in a new Developer Edition org and then link it to the Dev Hub through the Namespace Registries app, and "after you associate a namespace with an org, you can't change it or reuse it." Salesforce recommends "a single namespace for all of your managed 2GP packages," so one namespace can carry your core package and any extension packages you add. Our post on how a package namespace works covers naming.
The package type is the third. The Create a 2GP page says "You can't change the package namespace or package type after you create the package," so a package created as Unlocked by mistake can't become Managed.
| Decision | Where it lives | Can you change it later? |
|---|---|---|
| Dev Hub | Your Partner Business Org | No, and Dev Hub can't be turned off |
| Namespace | Registered in a Developer Edition org, linked to the Dev Hub | No, and it can't be reused |
| Package type and namespace on the package | The package record in the Dev Hub | No |
| Licensing | The License Management App, already installed in the PBO | Settings yes, the PBO link is set up once |
The License Management App (LMA) needs no install step. Salesforce's PBO reference says "The LMA is pre installed in PBOs," and the leads and licenses page says license records are generated only when a customer installs and "to receive licenses, you must also have the" LMA. The difference between the app and the org that holds it is in our post on the License Management Org and the License Management App.
Creating the first 2GP version with Salesforce CLI
The commands follow Salesforce's 2GP workflow. Run them against the PBO Dev Hub after the namespace is linked.
sf org login web --alias PBO --set-default-dev-hub
sf project generate --name myapp --output-dir .
sf package create --name "MyApp" --package-type Managed --path force-app --target-dev-hub PBO
sf package version create --package "MyApp" --code-coverage --installation-key "$PKG_KEY" --wait 60 --target-dev-hub PBOThe project file holds the namespace, the version number and the API version. For the first version there is no ancestor yet.
{
"packageDirectories": [
{
"path": "force-app",
"default": true,
"package": "MyApp",
"versionName": "First release",
"versionNumber": "1.0.0.NEXT"
}
],
"namespace": "myns",
"sourceApiVersion": "68.0",
"packageAliases": {
"MyApp": "0Ho..."
}
}Two flags on the version command matter more than they look. The package version create reference says "To promote a package version to released, you must use the --code-coverage parameter." It also requires either an installation key or --installation-key-bypass, and with the bypass "anyone can install your package." A key costs nothing during development and keeps a leaked 04t install link from becoming an install. Each package version is capped at 10,000 files and 600 MB of metadata, per the Create a 2GP page.
Test the beta in a scratch org, then promote it.
sf org create scratch --definition-file config/project-scratch-def.json --alias qa --duration-days 7 --target-dev-hub PBO
sf package install --package "[email protected]" --installation-key "$PKG_KEY" --target-org qa --wait 20
sf package version promote --package "[email protected]" --target-dev-hub PBOThe promote output gives the install URL in the form https://login.salesforce.com/packaging/installPackage.apexp?p0=04t..., as shown on Release a 2GP. Only this released version can go to security review, because Salesforce's required materials page says "We can't accept an unmanaged or beta package."What blocks a version from being promoted
Promotion is where a version becomes something customers can install and upgrade from, and Salesforce checks a short list of things first. Each blocker below comes from Get Ready to Promote or Code Coverage for 2GP.
| Blocker | What happens | Fix |
|---|---|---|
| Version built without --code-coverage | Promotion fails | Rebuild the version with the flag |
| Apex coverage under 75% | "A beta package version must pass a 75% code coverage requirement before it can be promoted" | Add tests, rebuild |
| A trigger with no test coverage | Fails, because "every Apex Trigger in a package needs test coverage" | Cover each trigger |
| Version built with --skip-validation | "you can't promote unvalidated package versions" | Rebuild without the flag |
| A second beta of the same minor version already promoted | "For every minor package version, you can promote only one beta version" | Bump the minor version |
The --skip-validation flag is the one that catches CI pipelines. It makes builds fast, so teams use it for every build, and then the build they want to release can't be promoted. A pattern that avoids it is skip-validation builds for feature branches and a full validated build, with coverage, on the release branch.
Promotion is also final. "After a package version is promoted to the released state, you can't reverse the promotion," says the same page. If a released version turns out to be broken, you can stop new installs by deleting the version or changing its installation key, but the fix ships as a new version.
How to ship an upgrade customers can install
An upgrade is a new version of the same package installed over an older one, and Salesforce only allows it along a line of ancestors. The upgrade page says "the new version must be a direct descendent of the package version installed in your org."
You set the ancestor in the project file. Salesforce's package ancestor page marks the HIGHEST keyword as recommended, because it "automatically sets the ancestor to the highest promoted package version number." For version 1.1 the package directory gains one line.
"versionNumber": "1.1.0.NEXT",
"ancestorVersion": "HIGHEST"Leaving the ancestor out is the most expensive mistake in 2GP versioning, because "existing customers can't upgrade to a package version that is created without a specified ancestor." The build succeeds, the new version installs cleanly in a fresh scratch org, and the problem only shows when a customer on 1.0 tries to move to 1.1. Only promoted versions can be ancestors, per Package Ancestors, and "packages can only upgrade along the ancestry line."
That is why the useful test is an upgrade, not an install. Create a scratch org, install the version customers have now, then install the new version over it and run your tests there. A fresh install never exercises the upgrade path, which is where removed fields, changed picklists and data migrations in post-install scripts break. Keep these tests in throwaway scratch orgs. Salesforce's install and uninstall page warns that because a beta can't be upgraded, "be sure you don't install it in a sandbox that you use in your release pipeline, such as UAT or staging."
Version numbers follow major.minor.patch.build. Patch versions are for fixes only, and the patch versions page says a patch can't add or delete package components. Patching is "available to only to packages that have passed AgentExchange security review," and you request it through a Partner Support case. Before review, every fix ships as a new minor version.
What locks once a version is released
Release turns parts of your package into a public contract with every org that installed it. The editable properties page says "the API names of packaged components are locked and can't be edited after the package version is promoted and released." For Apex the rule is wider. The Apex guide's global Apex best practices say "You generally can't change the API shape of global Apex after it's released in a managed package." Every class marked global for a quick fix becomes a method signature you support for as long as any customer runs the package, so keep global for what customers or other packages really call.
Removing a component is possible but uneven. Salesforce's remove metadata components page says "Most metadata components that were removed from a package version remain in the subscriber org after package upgrade and are marked as deprecated," and access to removal starts with a Partner Support case.
| Component removed from the package | What happens in the customer's org on upgrade |
|---|---|
| Apex class (not global) | Hard deleted |
| Apex trigger | Hard deleted |
| Custom object | Marked as deprecated, data stays |
| Custom field | Marked as deprecated, data stays |
Deprecated objects and fields keep running for existing customers and disappear for new ones, which is the same behavior the Deprecated annotation gives global Apex. Plan for it by naming objects and fields as if they are permanent, because for existing customers they are.
What changes when package classes move to API version 67.0
Raising a class's API version is a behavior change, not a housekeeping task. Salesforce's Apex security and sharing chapter lists three changes at API version 67.0.
The first breaks compilation. "In API version 67.0 and later, you can't use the WITH SECURITY_ENFORCED clause in SOQL SELECT queries in Apex code." Those queries move to WITH USER_MODE, which Salesforce's security review guidance names for enforcing field and object permissions.
The second changes who the code runs as. "In API version 67.0 and later, Apex runs in user context by default," where 66.0 and earlier default to system mode. The third changes what the code can see, because classes without an explicit sharing declaration now "run in with sharing mode." A class that relied on the old default to see every record will start returning fewer rows.
For a package that installs into hundreds of customer orgs with different sharing models, that third change is the one to test against real data shapes before release. Declare with sharing, without sharing or inherited sharing on every class so the API version never decides it for you.
Push upgrades or install links
There are two ways a released version reaches customers. An install link lets each customer's admin upgrade when they choose. A push upgrade installs the version into the orgs you pick, on a schedule you set.
Push upgrades come with prerequisites. Salesforce's Push a Package Upgrade for 2GP page says the feature "is only available to first- and second-generation managed packages that have passed the AgentExchange security review," and "to enable push upgrades for your managed package, log a case with Salesforce Partner Support." For 2GP you schedule pushes with the CLI or the API, because the Setup page for push upgrades only covers 1GP.
sf package push-upgrade schedule --package 04t... --start-time "2026-11-14T02:00:00" --org-file upgrade-orgs.csv --target-dev-hub PBOTwo details catch teams on their first push. The same page says "any new components included in the push upgrade package version are assigned only to admins," so new features need a permission set step or they stay invisible to users. And customers can block pushes only if you let them. Salesforce's customized push upgrades page says partners can "give customers the ability to restrict push upgrades," and "it's not possible to block push upgrades on a patch version of a package." Our post on how push upgrades handle customer customizations goes deeper on that setting.
Can you roll back a managed package version?
No. Salesforce documents no downgrade path for managed packages. Upgrades only move forward along the ancestry line, and promotion itself can't be reversed. The way back is a new, higher version that restores the old behavior, which is one more reason to test every upgrade on an org that holds the previous version.
Uninstalling is not a rollback either. The 2GP uninstall page says uninstalling deletes every component in the package, deprecated ones included. You also "can't uninstall a package whenever a component not included in the uninstall references any component in the package." Salesforce's post-install page says it creates an export file of the package data after an uninstall, and that file is "only available for a limited time." For a customer, uninstalling and reinstalling an older version means rebuilding their data and configuration.
1GP vs 2GP, and what to do with an existing 1GP package
Customers install the same kind of managed package either way. The generations differ in where the package lives and how you build versions, as Salesforce's 1GP and 2GP comparison lays out.
| 1GP | 2GP | |
|---|---|---|
| What owns the package | A packaging org, which "can own only one managed package" | The Dev Hub, which "can own one or more packages" |
| Namespace | One per package | One namespace can hold several packages |
| Source of truth | The packaging org's metadata | Your source control |
| How versions are built | Uploaded from Setup in the packaging org | sf package version create, then promote |
| Push upgrades | Setup UI or API, after review | CLI or API, after review |
| Status for new apps | Still supported | What Salesforce recommends |
For new apps Salesforce's managed packaging overview says "we recommend that everyone use managed 2GPs to create new apps." Existing 1GP packages keep working the way they always have, per Build and Release Your App with Managed Packages.
Moving an existing 1GP package uses Package Migrations. You convert it with sf package convert, then move customers with sf package push-upgrade schedule --migrate-to-2gp. Salesforce's migration workflow page says "Currently, you can use push upgrades to migrate 5 subscriber orgs per day." At that rate, moving 200 customer orgs by push takes 40 days of scheduled migrations, so the migration needs its own release plan. Our 1GP to 2GP migration guide covers the conversion steps and the version rules.
Mistakes that stop customers from upgrading
- A Dev Hub in a trial or Developer Edition org. When it expires, the packages go with it.
- No ancestor on version 1.1. It installs fine in new orgs and customers on 1.0 can't upgrade to it.
- Releasing from a skip-validation build. It can't be promoted, and the rebuild with coverage can surface failing tests on release day.
- Making classes global for convenience. Their signatures stay supported for every installed customer.
- Installing a beta in a UAT or staging sandbox. That sandbox can't take the next version until the beta is uninstalled.
- Testing installs instead of upgrades. A fresh scratch org never runs the upgrade path customers will run.
- Pushing a version that adds features without permissions. New components are assigned only to admins after a push.
Frequently Asked Questions
How long does it take to get a new managed package listed?
Plan for building, promoting and then security review. Salesforce puts an initial review at approximately 4 to 6 weeks after you submit, on its Act on Security Review Results page. Fixes and resubmissions come on top, as our guide to how long the security review takes explains.
Do managed package components count against a customer's org limits?
Partly, and only for packages listed on AgentExchange. Salesforce's install FAQ says packaged objects, fields, tabs and apps "don't count against your edition's allocation, but they do count against the total limit." The 6 MB Apex code limit doesn't apply to managed package code at all.
Do sandbox installs use up the customer's licenses?
No. The LMA's license object fields page says "When a package is installed in a sandbox org, Site License is the default," so sandbox testing doesn't draw down seats.
Can Appnigma build and maintain our managed package?
Yes. Appnigma's marketplace apps work covers the build plus "Salesforce security review preparation and responses." Ongoing upgrades are covered by "a separate maintenance retainer," and Salesforce controls review timing.
About the author
Sunny Chauhan is the co-founder and CEO of Appnigma AI, which builds and maintains customer integrations and native Salesforce and HubSpot apps for B2B SaaS teams. Before Appnigma, he was on Salesforce's AppExchange team, where he worked with more than 300 SaaS partners. He has spent close to a decade on the Salesforce platform, including work at Zennify, and holds Salesforce certifications including Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate.
Related Articles
- What a managed package is, and whether to ship one
- Moving a 1GP package to 2GP with Package Migrations
- Preparing a managed package for security review
- Governor limits a certified managed package gets
- Enforcing licenses in Lightning web components
- Building a managed package without an in-house Salesforce developer
Sources
- Enable Dev Hub in Your Org, Second-Generation Managed Packaging Developer Guide
- Create and Register Your Namespace and Link a Namespace to a Dev Hub Org
- Create a Second-Generation Managed Package and Workflow for 2GP
- package version create, Salesforce CLI Command Reference
- Get Ready to Promote and Release a 2GP Version and Code Coverage for 2GP
- Specify a Package Ancestor and Upgrade a 2GP Version
- Remove Metadata Components from 2GP and Editable Properties After Promotion or Installation
- Apex Security and Sharing and Global Apex best practices, Apex Developer Guide
- Push a Package Upgrade for 2GP and Enable a Package Subscriber to Restrict Push Upgrades
- Uninstall a 2GP and After Installing a Package
- Migrate Subscribers to 2GP, First-Generation Managed Packaging Developer Guide
- Salesforce Partner Business Org reference and License Object Fields
- Security Review Required Materials and Act on Security Review Results



