How to Create a Package and Versions in Salesforce DX

ליאור נכתב על ידי ליאור לביא, עודכן בתאריך 01/11/2023

What are Packages and Versions

Before we begin creating packages and versions, let's take a moment to understand what packages are and how they fit into our change management process in Salesforce.

Packages, as defined in Salesforce, are distributable units of functionality that can be wrapped and distributed to different environments in Salesforce for installation and use. A simple example of such functionality is a lightning web component (LWC) we have developed, and now we want to distribute it to our customers for installation in their different environments.

Versions are snapshots of our package at a specific point in time. A version is immutable, meaning that once created, it cannot be edited. When a customer installs our package in their environment, what they are actually installing is a specific version of our package.

Advantages of Transitioning to a Package-Based Development Model

Package-based development offers several advantages over other models, such as change sets or org-based development:

  1. Using packages allows us to start thinking about our organizational metadata in terms of well-defined and clear functionality, helping us identify places where there are incorrect dependencies between classes in our code.
  2. Packages, unlike change sets, do not use deltas to define changes. Each Package contains all its metadata, whether it has changed during the latest development or not. This means we don't need to track specific metadata components and ensure their existence in the target environment.
  3. Since packages are largely standalone, as they contain all metadata they are based on, we can use a special type of environment,  a Scratch org, to install and test them. Scratch orgs are clean environments with no custom configurations and data, ensuring that we haven't accidentally relied on specific development configurations or unique data.
  4. Using packages allows us to define a separate release schedule for each package, as each package is developed separately. This means we can prioritize developments much more granularly and ensure that high-priority developments do not get stuck in a pipeline waiting for low-priority developments that use shared metadata components.

Now that we understand the advantages of using a package-based development model, let's learn how to create and install a new package. To do this, we will perform the following steps:

  1. Create a new SFDX project in Visual Studio Code using Salesforce DX.
  2. Create a simple LWC (Lightning Web Component).
  3. Create a Trailhead playground environment that will serve as our Dev Hub, enabling us to create packages and scratch orgs for testing.
  4. Create the initial package and version.
  5. Create a Scratch org for testing our LWC in a clean environment.
  6. Install our version on the scratch org and manually verify that it works as expected.
  7. Install our version in our Trailhead environment and manually verify that it works as expected.
  8. Make a change to our LWC, create a new version for it, and install it on our scratch org for testing.
  9. Make our latest version available for deployment in production environments.

Creating a New SFDX Project in Visual Studio Code

To create a new SFDX project in Visual Studio Code, follow these steps:

  1. Open Visual Studio Code.
  2. Press Ctrl + Shift + P on your keyboard to open the command palette in VS Code.
  3. Type "Project" and select "SFDX: Create Project" from the options.
  4. Choose the "Standard" project template.
  5. Give your project a name, for example, "MyFirstPackage."
  6. Select the directory where you want to save your new project.
  7. If VS Code displays a screen with the title "Do you trust the authors of the files in this folder?", click "Yes, I trust the authors."

Creating a New LWC (Lightning Web Component)

  1. Right-click on the force-app\main\default\lwc directory and choose "SFDX: Create Lightning Web Component."
  2. Give a name to your LWC, for example, "HelloWorld."
  3. Choose the destination directory where the component will be saved. In this example, we choose the first option presented by VS Code, which is the default directory for LWC components, "force-app\main\default\lwc."
  4. In the "helloWorld.html" file, enter the following code:
        <template>
            <lightning-card title="My Name">
                <div class="slds-p-around_medium">
                    <lightning-input type="text" label="Enter your name" value={name} onchange={setName}></lightning-input>
                </div>
                <span class="slds-p-around_medium">Hello World! My name is <b>{name}</b></span>
            </lightning-card>
        </template>
    
  5. In the "helloWorld.js" file, enter the following code:
        import { LightningElement } from 'lwc';
        
        export default class HelloWorld extends LightningElement {
            name;
            setName(event){
                this.name = event.target.value;
            }
        }
  6. In the "helloWorld.js-meta.xml" file, enter the following code:
        <?xml version="1.0" encoding="UTF-8"?>
        <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
            <apiVersion>55.0</apiVersion>
            <isExposed>true</isExposed>
            <targets>
                <target>lightning__AppPage</target>
                <target>lightning__HomePage</target>
                <target>lightning__RecordPage</target>
            </targets>
        </LightningComponentBundle>

Creating a Trailhead Playground

To create a Trailhead playground environment that will serve as our Dev Hub and enable us to create packages and scratch orgs, follow these steps:

  1. Log in to Trailhead. If you don't have a Trailhead account, follow this guide to create a Trailhead account and a new playground environment.
  2. Click on your name in the upper right corner of the screen, next to your profile picture, and select "Hands-On Orgs" from the menu.
  1. Click on the Create Playground button.
  2. Give a name to the new environment, for example, Package Testing, and click Create.
  3. Wait for Salesforce to finish creating the environment. This may take a few minutes. Waiting for New Trailhead Playground
  4. Once the environment is created, click on the Launch button next to its name to open it.
  5. After the environment is opened, click on the Get Your Login Credentials tab.
  6. In the Get Your Login Credentials tab, copy your username and save it for later; we will need it in the following steps.
  7. Click the Reset My Password button. In the pop-up window, click OK.
  8. Open the email associated with your Trailhead account and find the email with the subject "Finish resetting your Your Developer Edition password."
  9. Click the link in the email to reset your password.
  10. In the browser tab that opens, click Reset Password.
  11. In the Change Your Password screen, choose a new password and click Change Password.

Great! You've created a new playground environment. Now, let's set it up as a Dev Hub so that we can create packages and scratch orgs.

  1. In the new environment, click on the gear icon next to the bell icon and your profile picture, and select Setup.
  2. In the tab that opens, at the top left, in the Quick Find search field, enter the words "Dev Hub" and click on the result under Dev Hub under Development.
  3. Move the Enable Dev Hub button to the Enabled state. Note! Once you enable this option, you cannot disable it!
  4. Move the Enable Unlocked Packages and Second-Generation Managed Packages button to the Enabled state. Note! Once you enable this option, you cannot disable it!

Creating a Package and Version

In VS Code, perform the following steps to create the initial package and version:

  1. In the VS Code Explorer panel on the left, which displays project files, open the sfdx-project.json file located in the project's root directory, alongside files like README.md and package.json.
  2. Ensure that the content of the file looks similar to the following code. Your sourceApiVersion number may be different, and that's fine.
    {
          "packageDirectories": [
            {
              "path": "force-app",
              "default": true
            }
          ],
          "name": "MyFirstPackage",
          "namespace": "",
          "sfdcLoginUrl": "https://login.salesforce.com",
          "sourceApiVersion": "55.0"
        }
  3. If your terminal is not open in VS Code, press the shortcut Ctrl + ~ to open it.
  4. Enter the following command in the Terminal to open and connect your Trailhead playground that we created to your project as a Dev Hub:
    sfdx org web login -d -a MyTP
    Note the -d flag; it defines the org to which we connect, our Trailhead playground, as the default Dev Hub environment for creating scratch orgs and packages in the project.
  5. In the browser tab that opens, enter your username and the password for your Trailhead playground. If the browser asks for permission with an "Allow Access?" message, click the Allow button. You should see a success message in the terminal when you enter the environment.
  6. Enter the following command in the Terminal.
    sfdx package create -n MyFirstPackage -t Unlocked -r force-app -d "My awesome first package!"
    The success message with the Package ID should appear in the Terminal.
    If you look at the content of the sfdx-project.json file, you will see that variables describing the package's name and version and a package aliases with our package name and it ID that starts with "0Ho".
  7. Enter the following command in the Terminal to see the list of Packages associated with the Dev Hub environment defined by default for creating scratch orgs and packages in your project. You should see only one package, named MyFirstPackage.
    sfdx package list
    Package list.PNGCongratulations! You've created a package! Next step: creating a version.
  8. In the project's root directory, open the sfdx-project.json file. Your file should look something like this:
    {
            "packageDirectories": [
                {
                    "path": "force-app",
                    "default": true,
                    "package": "MyFirstPackage",
                    "versionName": "ver 0.1",
                    "versionNumber": "0.1.0.NEXT"
                }
            ],
            "name": "MyFirstPackage",
            "namespace": "",
            "sfdcLoginUrl": "https://login.salesforce.com",
            "sourceApiVersion": "55.0",
            "packageAliases": {
                "MyFirstPackage": "0Ho***"
            }
        }
  9. Change the values in the following fields:
    1. versionName - from "ver 0.1" to "Version 1.0". The versionName field holds the name of the version, and it can be any text you prefer. If you want, you can use the expression "Summer '22" as the version name, as Salesforce does.
    2. versionNumber - from "0.1.0.NEXT" to "1.0.0.NEXT". This field holds the version number in the format of major.minor.patch.build. The "NEXT" term allows us to increase the build number by one each time we create a new version.
    After making these changes, your code should look something like this:
    {
            "packageDirectories": [
                {
                    "path": "force-app",
                    "default": true,
                    "package": "MyFirstPackage",
                    "versionName": "Version 1.0",
                    "versionNumber": "1.0.0.NEXT"
                }
            ],
            "name": "MyFirstPackage",
            "namespace": "",
            "sfdcLoginUrl": "https://login.salesforce.com",
            "sourceApiVersion": "55.0",
            "packageAliases": {
                "MyFirstPackage": "0Ho***"
            }
        }
  10. To create a new Version, enter the following command in the Terminal:
    sfdx package version create -p MyFirstPackage -d force-app -k test1234 -w 10
    Explanation of the flags in the command:
    p - the name of the package for which we are creating a Version.
    d - the directory where the package is located.
    k - the installation key for protected versions. Using this key allows us to define who can install the version we create.
    w - setting wait time makes the creation of the version synchronous, and the terminal will show a progress status update every few seconds until the version creation is complete or fails. The terminal will display the progress status for the number of minutes specified by the wait flag, in our example, 10 minutes. After the version is successfully created, the command will automatically update the sfdx-project.json file with the new version's name and ID under the packageAliases parameter. If you don't specify this flag, you will get the following line back from the Terminal:
    Package version creation request status is 'Initializing'. Run "sfdx force:package:version:create:report -i 08c***" to query for status.
    Copying the command received in the output of the creation command allows you to get a detailed report in the terminal of the new version, but the sfdx-project.json file won't be updated automatically, and you will need to update the packageAliases manually.
  1. After creating the version, to view the version you created, enter the following command in the Terminal:
    sfdx package version list
    Version List We've completed the version creation! Excellent! Now, let's create a scratch org for installation and testing of our LWC.

Creating a Scratch Org

A scratch org is a temporary environment, quick to set up and completely clean of custom metadata and data, making it perfect for focused testing.

To create a scratch org, follow these steps:

  1. In our project, under the config folder, there is a file named project-scratch-def.json. This file contains the settings for scratch org environments in our project. The code in the file should look something like this:
    {
                "orgName": "Demo company",
                "edition": "Developer",
                "features": ["EnableSetPasswordInApi"],
                "settings": {
                    "lightningExperienceSettings": {
                    "enableS1DesktopEnabled": true
                    },
                    "mobileSettings": {
                    "enableS1EncryptedStoragePref2": false
                    }
                }
                }

    To start cleanly, change the orgName to a different value, for example, My Cool Company, and remove the "EnableSetPasswordInApi" entry so that the features section remains empty.

    {
                "orgName": "My Cool Company",
                "edition": "Developer",
                "features": [],
                "settings": {
                    "lightningExperienceSettings": {
                    "enableS1DesktopEnabled": true
                    },
                    "mobileSettings": {
                    "enableS1EncryptedStoragePref2": false
                    }
                }
                }
  2. To create a new Scratch org, enter the following command in the Terminal:
    sfdx org create scratch -d -f config/project-scratch-def.json

    The org create scratch command creates a new scratch org in Salesforce. The -d flag indicates that this org is the current project's default org, and all commands that require an org are directed to it, and the -f flag points the command to the settings file for scratch org in our project.

  3. To open the Scratch org we created in a browser tab, enter the following command:
    sfdx org open
    Since we've set our scratch org as the project's default org, the open command doesn't require specifying the org destination.
  4. In the page that opens for our scratch org, in the Quick Find box, enter the term "Installed Packages." Click the link you find under Installed Packages. The list of Installed packages should be empty.

Installing a Version in Our Scratch Org

Enter the following command to install the latest version we created in our scratch org:

sfdx  package install --wait 10 --publish-wait 10 --package MyFirstPackage@1.0.0-1 -k test1234 --no-prompt

Explanation of the flags in the command:

  1. wait - synchronous installation of the version in a number of minutes. In the example above, the installation will take place immediately, synchronously, and will have 10 minutes to complete. If more than 10 minutes have passed and the installation has not completed, you will see the following message while the installation process continues to run in the background.
    PackageInstallRequest is currently InProgress. You can continue to query the status using
    sfdx force:package:install:report -i 0Hf*** -u test-***@example.com
  2. publish-wait - when we try to install a new version, Salesforce takes a few minutes to ensure that the version exists in the org before attempting to install it. The publish-wait flag specifies how long Salesforce should wait for the version to be present in the org before the system tries to install it.

Refresh the page in your browser for Installed packages and see that the package has been added to the list of installed packages.

Deploying Our LWC in the Scratch Org for Testing

Now, let's check that our installation was successful and that our component works as expected.

The component we wrote is relatively simple; it creates a UI element of the Card type containing a text input field. When the user enters a name in it, the sentence "Hello World! My name is" below the input field is automatically completed with the name entered by the user, in bold font.

Follow these steps to verify that our component was successfully installed:

  1. Sign in to the Sales application in your Scratch org.
  2. Go to the Home page.
  3. Click the gear icon next to your profile picture in the upper right corner of the screen and select Edit Page.
  4. From the list of Components on the left side of the screen, scroll down and find the helloWorld component under Custom.
  5. Drag and drop the component wherever you like on the Home page.
  6. Click Save.
  7. If you see a message about activating the page, follow these steps:
    1. Click the Activate button.
    2. In the window that opens, Activation: Home Page Default, click the Assign as Org Default button.
    3. In the Set as Org Default: Home Page Default window, click Save.
  8. Return to the Home page by clicking the Back arrow on the left side of the Lightning App Builder title.
  9. The component with the label "My Name" should appear on the page. Enter a name in the "Enter your name" field and check that the "Hello World! My name is" line is automatically completed with the name you entered in bold.

Now, let's check that our component was successfully installed in the Trailhead environment as well.

Deploying Our LWC in the Trailhead Environment for Testing

  1. Enter the following command in the Terminal to install the version in the Trailhead environment. Pay attention to the -o flag, which points to the Trailhead playground environment.
    sfdx package install --wait 10 --publish-wait 10 --package MyFirstPackage@1.0.0-1 -k test1234 --no-prompt -o MyTP
  2. Enter the following command in the Terminal to open your Trailhead environment in a browser tab.
    sfdx org open -o MyTP
  3. Sign in to the Sales application in your Trailhead environment.
  4. Go to the Home page.
  5. Click the gear icon next to your profile picture in the upper right corner of the screen and select Edit Page.
  6. From the list of Components on the left side of the screen, scroll down and find the helloWorld component under Custom.
  7. Drag and drop the component wherever you like on the Home page.
  8. Click Save.
  9. If you see a message about activating the page, follow these steps:
    1. Click the Activate button.
    2. In the window that opens, Activation: Home Page Default, click the Assign as Org Default button.
    3. In the Set as Org Default: Home Page Default window, click Save.
  1. Return to the home screen by clicking the Back arrow next to the title "Lightning App Builder" on the left side of the page.
  2. The component with the label My Name should appear on the page. Enter your name in the Enter your name field and check that the "Hello World! My name is" line is automatically completed with the name you entered in bold.

Congratulations! We've created an LWC, created a package and a version for it, installed and tested it in a scratch org and the Trailhead playground of the Developer Edition.

Now, let's learn how to make a code change, create a new version for that change, and install it in our scratch org for testing.

Creating a New Version and Testing it in the Scratch Org

To make a change and create a new version, follow these steps:

  1. First, make a small change to our code. In the helloWorld.html file, add inline styling so that the text entered by the user in the text box appears in red below it as follows:
    <template>
        <lightning-card title="My Name">
            <div class="slds-p-around_medium">
                <lightning-input type="text" label="Enter your name" value={name} onchange={setName}></lightning-input>
            </div>
            <span class="slds-p-around_medium">Hello World! My name is <b style="color: red;">{name}</b></span>
        </lightning-card>
    </template>
  2. Save the change you made.
  3. Now, create a new version. Enter the following command in the Terminal to create a new version of our package. After the version is created, you can view it in the packageAliases list in the sfdx-project.json file.
    sfdx package version create -p MyFirstPackage -d force-app -k test1234 -w 10
  4. Enter the following command in the Terminal to install the new version in our scratch org. Note that our build number has increased from 1 to 2.
    sfdx force:package:install --wait 10 --publish-wait 10 --package MyFirstPackage@1.0.0-2 -k test1234 --no-prompt
  5. Open the Installed Packages page in your scratch org's Setup and see that the Version Number has increased from 1.0 (Beta 1) to 1.0 (Beta 2)
  6. Open or refresh the home page of the Sales application in your Scratch org.
  7. Enter your name in the "Enter your name" text box and make sure your name appears in red at the end of the sentence "Hello World! My name is" below the text box.

Promoting Our Latest Version for Installation in Production Environments

So far, the versions we've created for our package are beta versions and cannot be installed in production environments. Follow these steps to promote a version for installation in production environments:

  1. Create a new version with code coverage as follows:
    sfdx package version create -p MyFirstPackage -d force-app -k test1234 -w 10 --code-coverage

    Code coverage of over 75% is a precondition before promoting a version to production. Keep in mind that creating a version with code coverage takes more time than creating a version without code coverage. When version creation takes longer than the time set using the -w flag, the Terminal displays a message like this:

    Package version creation request status is 'Verifying metadata'. Run "sfdx force:package:version:create:report -i 08c***" to query for status.

    We can monitor the progress of version creation by running the following command and checking the Status field in its output:

    sfdx force:package:version:create:report -i 08c***
  2. (Optional) Run the following command to check the code coverage percentage of the version we created. The command will create a log file with a .txt extension in the directory where the command was executed. You can open the text file to conveniently read the information.
    sfdx package version list --verbose > log.txt
    The --verbose flag will display detailed information about all the versions associated with our Dev Hub. The > sign will print this output to a text file. The text file should look like this:
    Using specified username lior***@mindful-narwhal-***.com
    
    === Package Versions [3]
     Package Name   Namespace Version Name Version Subscriber Package Version Id Alias                  Installation Key Released Validation Skipped Ancestor Ancestor Version Branch Package Id         Installation URL                                                                  Package Version Id Created Date     Last Modified Date Tag Description Code Coverage Code Coverage Met Converted From Version Id Org-Dependent Unlocked Package Release Version Build Duration in Seconds Managed Metadata Removed Created By         
     ────────────── ───────── ──────────── ─────── ───────────────────────────── ────────────────────── ──────────────── ──────── ────────────────── ──────── ──────────────── ────── ────────────────── ───────────────────────────────────────────────────────────────────────────────── ────────────────── ──────────────── ────────────────── ─── ─────────── ───────────── ───────────────── ───────────────────────── ────────────────────────────── ─────────────── ───────────────────────── ──────────────────────── ────────────────── 
     MyFirstPackage           Version 1    1.0.0.1 04t***                        MyFirstPackage@1.0.0-1 true             false    false              N/A      N/A                     0Ho***             https://login.salesforce.com/packaging/installPackage.apexp?p0=04t***             05i***             2022-08-21 17:21 2022-08-21 17:21                                 false                                       No                             55.0            42                        N/A                      005*** 
     MyFirstPackage           Version 1    1.0.0.2 04t***                        MyFirstPackage@1.0.0-2 true             false    false              N/A      N/A                     0Ho***             https://login.salesforce.com/packaging/installPackage.apexp?p0=04t***             05i***             2022-08-21 17:36 2022-08-21 17:36                                 false                                       No                             55.0            45                        N/A                      005***
     MyFirstPackage           Version 1    1.0.0.3 04t***                                               true             false    false              N/A      N/A                     0Ho***             https://login.salesforce.com/packaging/installPackage.apexp?p0=04t***             05i***             2022-09-07 15:23 2022-09-07 15:23                   100%          true                                        No                             55.0            723                       N/A                      005***
    
    Pay attention to our code coverage (highlighted in black), which is equal to 100% in this example. Additionally, you can see that the value in the Released column is False. We want to turn it to True so that we can install the version in production environments.
  3. If the version creation time was extended, and you had to run the command sfdx force:package:version:create:report, it means that your project file, sfdx-project.json, was not updated automatically with the name of your new version. You'll need to add it manually. First, run the following command to copy the Subscriber Package Version Id of your new version from the table:
    sfdx force:package:version:list
    sfdx-project.json - Old Version
    {
        "packageDirectories": [
            {
                "path": "force-app",
                "default": true,
                "package": "MyFirstPackage",
                "versionName": "Version 1",
                "versionNumber": "1.0.0.NEXT"
            }
        ],
        "name": "MyFirstPackage",
        "namespace": "",
        "sfdcLoginUrl": "https://login.salesforce.com",
        "sourceApiVersion": "55.0",
        "packageAliases": {
            "MyFirstPackage": "0Ho***",
            "MyFirstPackage@1.0.0-1": "04t***",
            "MyFirstPackage@1.0.0-2": "04t***"
        }
    }
    sfdx-project.json - New Version
    {
        "packageDirectories": [
            {
                "path": "force-app",
                "default": true,
                "package": "MyFirstPackage",
                "versionName": "Version 1",
                "versionNumber": "1.0.0.NEXT"
            }
        ],
        "name": "MyFirstPackage",
        "namespace": "",
        "sfdcLoginUrl": "https://login.salesforce.com",
        "sourceApiVersion": "55.0",
        "packageAliases": {
            "MyFirstPackage": "0Ho***",
            "MyFirstPackage@1.0.0-1": "04t***",
            "MyFirstPackage@1.0.0-2": "04t***",
            "MyFirstPackage@1.0.0-3": "04t***"
        }
    }
  4. To promote our version to the Released state, use the Promote command as follows:
    sfdx package version promote -p MyFirstPackage@1.0.0-3 -v MyTP
    The Terminal will display the following message and ask if you are sure you want to release your package version and if you understand that it's an irreversible action. Type y and press Enter to confirm.
    Are you sure you want to release package version MyFirstPackage@1.0.0-2? You can't undo this action. Release package (y/n)?:
    After promoting the version, the Terminal will display the following message:
    Successfully promoted the package version, ID: 04t***, to released. Starting in Winter ‘21, only unlocked package versions that have met the minimum 75% code coverage requirement can be promoted. Code coverage minimums aren’t enforced on org-dependent unlocked packages.
    
    That's it! Now, you can install your version in production environments!