AppExchange partners, do you ever wish you had a single resource that explains how to get started quickly with modern AppExchange development? If so, read on!
Whether you’re developing your first AppExchange App, or you’ve been doing it since AppExchange launched in 2005, this blog post will give you clear, prescriptive guidance for how to set up your org landscape and development pipeline to maximize future success.
Here’s an overview of the modern AppExchange development process. We’ll dive into each of these steps in more detail below.
One-time setup in three steps
This section explains one-time actions that will set you up for success.
Step 1: Use second-generation managed packaging
The choice between creating a first-generation managed package (1GP) and a second-generation managed package (2GP) arises early in the journey of creating an AppExchange app. So early that it sneaks up on some developers, who decide without realizing the difference between the two or the consequences of this decision.
So, let’s be clear and upfront: all new packages should be 2GP. As of 2024, nobody should create any new 1GPs.
1GPs were state-of-the-art when they were introduced in 2005. Since then, the term DevOps was coined, and development lifecycle practices and technology have had a renaissance. 2GP was purpose-built to fix the biggest pain points our partners had with 1GP, and the gap between the two technologies is widening with each release.
To learn more about each of the above advantages of 2GP, see the 2GP developer guide section Why Switch to Second-Generation Managed Packaging and the video playlist 2GP Deep Dive: Features & Use Cases. The blog post Accelerate Your Package Builds by Leveraging Different Build Types highlights an example of new “2GP-only” features designed to speed up the ISV dev lifecycle.
Step 2: Use the Dev Hub in your Partner Business Org
A Partner Business Org (PBO) is a Salesforce org that is typically given to your company when its first employee joins Salesforce’s Partner Community. Your PBO contains:
- Tools for setting up and managing an AppExchange ISV business
- User licenses designed for 2GP developers
- Higher limits for scratch org and 2GP operations
- Additional benefits
If you are unsure whether or not you received a PBO, verify that you have one and, if necessary, request one.
Note that your PBO starts as a trial org, which expires in one year. Once your company has signed Salesforce’s Partner Application Distribution Agreement (PADA), open a case to convert this to an active, permanent org.
Dev Hub is a feature within an org that lets you create and manage scratch orgs, 2GP packages, and namespaces. This requires a one-time setup in your PBO, by:
- Turning on Dev Hub and 2GP
- Creating a Developer Edition org using Environment Hub
- Creating a namespace in this Developer Edition org
- Linking that namespace from your PBO, which enables you to create 2GP packages that use that namespace
Step 3: Create a Salesforce DX project
A Salesforce DX project is a folder and file structure that VS Code, Code Builder, or the Salesforce CLI can create on your laptop, so that you can work with source-driven development on one or more 2GP packages. It has a standard set of files that are required for Salesforce’s IDE extensions’ and CLI’s packaging, scratch org, and metadata retrieve and deploy functionality to work. Put the whole project in source control as soon as you create it.
When you create a project, a folder named “force-app” is created. This folder determines what will be in your package, so this is where you need to add the metadata XML files that you want to package before creating a package version.
There are two important files to know about within your project: sfdx-project.json
and project-scratch-def.json
.
The sfdx-project.json
file (see docs) is where you specify your package namespace, and dependencies on other packages, if any.
The project-scratch-def.json
file is where you define the edition, features, and settings of the scratch orgs that you create. This file is also used during the 2GP version creation process to validate that the package version being created can be successfully installed into an org of this shape. By “shape,” we mean with the edition, features, and settings specified in this file.
Pro tip: Only add features that are absolutely required for your package to install successfully to project-scratch-def.json
. This avoids introducing accidental dependencies on paid or uncommon features of Salesforce, which would reduce the number of potential customers who can install or buy your app.
Note that dependencies on Salesforce features are a separate concept from dependencies on other packages in a multi-package solution.
The five keys to modern AppExchange development
In this section, we’ll share our most critical guidance for an efficient development lifecycle.
1) Know whether or not to namespace your scratch orgs
Use namespaced scratch orgs for development. Since your metadata will be namespaced once it is packaged, it is a smoother experience if you create the metadata using a namespaced scratch org that has the same namespace as your package.
Namespaced scratch orgs also have the concepts of ancestry and manageability rules built into them. More on this later, but importantly, this means that attempting to make changes that would cause errors later during the creation of a new package version will instead be caught right away during development, making them quicker and easier to fix.
Once a feature is complete, retrieve it from the scratch org using the org’s built-in source tracking with sf project retrieve start
, so you don’t have to remember every piece of metadata you changed. Then commit these changes to source control, push them to your remote repo, and open a pull request. We recommend setting up CI/CD, which can trigger automation like Code Analyzer, run scripts to test package version creation, install, and upgrade, run Apex and Jest tests, etc. This helps you find and fix issues as early as possible, saving time and money.
Use non-namespaced scratch orgs to test package installation, upgrade, or run CI/CD. This mimics a customer org, which will not have a namespace. Namespaced is the default option when creating a scratch org via the CLI or IDE extensions, so to create a non-namespaced scratch org, use the CLI and add the --no-namespace
flag.
The above diagram is a summary of how scratch orgs fit into the 2GP development lifecycle. See a walkthrough to understand the full lifecycle and CLI commands to run each step of the way.
Note: you may hear the term “packaging org” from time to time. This is a 1GP-specific term, since with 1GP, a package could only be created and managed from within an org. With 2GP, since you use the CLI to manage the full packaging lifecycle, and since version control (specifically the force-app folder) is the source of truth for package contents, there is no packaging org.
2) Know when to use Environment Hub
If you can perform QA, UAT, and all other non-automated forms of testing in non-namespaced scratch orgs without too much manual setup, do that. The scratch org definition file, and if necessary, Scratch Org Snapshots and open source tools such as CumulusCI make it easier to automate setting up scratch orgs for testing purposes.
If you find that you need a longer-lived org for this, use your PBO’s Environment Hub (setting the “Purpose” field to “Test/Demo”) to create a longer-lived org, known as a Trial org, for this type of testing.
If you find that this testing requires manual setup and you expect to do this often, create a Trialforce Source Org (TSO), install packages, configure metadata, and load data as necessary, and create a template from it. You can then use this template’s ID in Environment Hub to create copies of this org, so you don’t have to repeat the manual setup for each org.
This is also how you create orgs to demo your app to customers, and how you use the Partner Console to allow customer trials of your app from your AppExchange listing.
3) Understand beta vs. released package versions
When a 2GP version is created, it starts out as a beta package version. Beta versions cannot be installed into production orgs. You can upgrade a subscriber installation of your package to a beta version, but you can’t upgrade a subscriber installation from a beta version, so be careful where you install betas. You can list a beta version as a dependency, but you can not use a beta version as an ancestor.
Promote a beta version to released once you need to install it in long-lived subscriber orgs and want these orgs to have an upgrade path (e.g., your TSOs or Trial orgs, if you consistently use the same orgs for QA, UAT, or similar testing use cases). Only released versions can be listed on AppExchange or installed in customer production orgs.
4) Understand ancestry basics
When you’re starting out with 2GP, we recommend using the default option, “linear ancestry,” until you have a reason not to. After you promote a version of your package for the first time, add a line "ancestorVersion": "HIGHEST",
(see docs) to your sfdx-project.json
file to make sure any existing subscriber installations will be upgradeable to future package versions that you create.
You would only need to change this in the future if you promoted a version and then realized you’d like to abandon the changes you made in that version. For example, you were experimenting with some new features in a beta version and accidentally promoted the version. Abandoning a version is also known as breaking ancestry.
Warning: do not abandon a version if any customers have it installed in production. Doing so will leave them without an upgrade path. Learn more in the Developer Guide.
5) Bookmark the right resources
Imagine yourself in a future where you hear about a new product or feature of Salesforce, and you’d like to experiment with it and perhaps add it to your package. Your future self will thank you if you bookmark the following pages in a folder together:
- Metadata Coverage Report: The source of truth for which metadata types are 2GP packageable
- Components Available in Managed Packages: For metadata types that are packageable, this doc provides more detail about how that metadata type behaves in managed packages, e.g., whether it is removable from packages, is subscriber editable, has any known issues, etc
- Build Your Own Scratch Org Definition File: Provides a sample scratch org definition file and lists all options available to determine the shape of your scratch org
- Scratch Org Features: Lists the Salesforce add-on features that you can enable in a scratch org when you create one
- Scratch Org Settings: Lists the Setup menu settings that you can preconfigure in a scratch org when you create one
Bonus: For clear guidance on the last step of our process, watch the video How to Submit for Security Review so you can list on AppExchange.
Help, I Started Developing a 1GP by Mistake
Let’s bring some of these concepts to life with an example. Say that you are an ISV Partner who developed your new app as a 1GP before you realized that 2GP existed. Your app has not yet passed the AppExchange Security Review, and you have not yet installed it into any customer production orgs.
You encounter the below message while preparing for Security Review.
If this is you, good news! It is easy to replace your 1GP with a 2GP by following the steps below.
Warning: If your 1GP is installed in one or more customer production orgs, then we do not recommend using this process to replace it with a 2GP. Instead, join the Partner Community’s 1GP → 2GP Migrations group to learn when you will be able to move your package and customer installs to 2GP.
- If necessary, become familiar with VS Code or Code Builder. Watch this video showing a detailed, hands-on look at what we will do in the following steps. Use the embedded terminal to run the CLI commands below.
- Get the contents of your 1GP into your project folder:
- If you have the source of your 1GP in a git repo (nice work!), clone it to your local machine by running:
git clone myRepoURLGoesHere
- If you don’t, we will retrieve it from your 1GP packaging org. First create a project with
sf project generate --name my-local-project-name
andcd my-local-project-name
. Then, usesf org login web --instance-url https://login.salesforce.com
to authenticate the Salesforce CLI to your 1GP packaging org. Replace the instance URL with your org’s My Domain custom login URL if needed. Finally, run the commandsf project retrieve start --package-name "Your1gpPackageNameGoesHere"
and voila! You now have the contents of your 1GP in your soon-to-be 2GP force-app folder.
- If you have the source of your 1GP in a git repo (nice work!), clone it to your local machine by running:
- Follow the steps to link the namespace of your 1GP packaging org from within your PBO Dev Hub.
- Follow the steps to create your 2GP, create a 2GP version, and once tested and ready, promote that version to released. You are now done with the 2GP lifecycle!
- Next, follow the steps to make sure your PBO Dev Hub is connected to the Partner Console. You may also wish to register your 2GP to manage licenses.
- Follow the steps to submit for Security Review.
- Do not use your 1GP any longer. It is effectively abandoned and replaced by your 2GP at this point.
Conclusion
While there are many more advanced concepts to consider as your AppExchange app matures and scales, the prescriptive guidance in this blog post will help you establish a strong foundation for your DevOps process.
Have questions? Want to discuss this post? Join us in the Partner Community’s Managed Packages group.
Resources
- Video: Build Apps Using 2GP
- Video Playlist: 2GP Deep Dive Series: Features & Use Cases
- Trailhead: Second-Generation Managed Packages
- Trailhead: Code Builder Basics
- Trailhead: Quick Start: Visual Studio Code
- Trailhead: Command-Line Interface Basics
- Trailhead: Git and GitHub Basics
- Developer Guide: Managed 2GP
- Developer Guide: Workflow for 2GP
- Blog Post: Everything You Need to Know to Build a Great AppExchange App
- Trailhead: Build Apps with CumulusCI
About the author
James Quinn is a Director of Product Management focused on making the Salesforce Platform extensible and easy to develop custom apps on. He strives to make partners’ and customers’ lives easier by actively listening to the community, driving developer experience standards across products internally, and rallying teams around a developer-focused product vision. Connect with him on LinkedIn.