Getting Started with Salesforce DX (Part 3 of 5)

This is the third installment in a five-part series, looking at the changes and opportunities Salesforce DX offers app developers today. Over the course of this series, we’ll talk about:

The content in this series is a collaboration between the Salesforce DX and Salesforce Evangelism teams.

It’s the Salesforce CLI–not the Salesforce DX CLI

Salesforce DX is often referred to as SFDX or sfdx. One reason is because the executable command for the Salesforce CLI is sfdx (another is that we humans like abbreviations). And with that in mind people often think that the Salesforce CLI can only be used with Salesforce DX projects – which isn’t true.

The Salesforce CLI is, as the name says, a command line interface that enables you to run numerous tasks on your Salesforce environments. A major portion of the current functionality is targeted at elements that were introduced with Salesforce DX, like source-driven development using scratch orgs or IDE integration and ALM. However, there’s a lot more going on and available across any type of org.

For example, you can use the CLI to run a SOQL query against a production org.

As you can see, this isn’t dependent on scratch orgs or other new functionalities at all. It’s a standardized— and Salesforce-supported— way of working with any org. Think of it like using Ant with the Migration Tool, but as a modern tooling.

Wait, what, I can use the CLI with any org?

Yes, you can! By design, not all commands work with every org type. For example, you can’t pull source data from a non-scratch org. But first things first. When you run sfdx force:org:list you get a list of all your orgs, that you authenticated against using the CLI.

=== Orgs
     ALIAS         USERNAME                                 ORG ID              CONNECTED STATUS
───  ────────────  ───────────────────────────────────────  ──────────────────  ────────────────
(D)  DevHubDF17                         00D1I000001frNvUAI  Connected
     GS0 Hub                          00DB00000005F5JMAU  Connected
     SFDX1 Org     rene@sfdx.test                           00D0Y000000ZDFzUAO  Connected

ALIAS      SCRATCH ORG NAME                 USERNAME                                               ORG ID              EXPIRATION DATE
─────────  ───────────────────────────────  ─────────────────────────────────────────────────────  ──────────────────  ───────────────
einstein2  einstein-ai                                  00D0R000000Ct3BUAS  2018-01-13

This abbreviated list of my current orgs shows that I have authenticated the CLI against three non-scratch orgs (lines 4-6). This can be, for example, your production org, a Sandbox org or a Developer Edition org. Check out the last column for the scratch org, which shows the expiration date of that org.

➜ ~ sfdx force:auth:web:login -a weather
Successfully authorized rene@weather.tdx with org ID 00D0Y000002EzZbUAK
You may now close the browser

The command above demonstrates how to use the CLI to authenticate into a non-scratch org and give it the convenient alias “weather” that can be used to refer to that org later (aliases are great so you don’t have to remember usernames). The CLI opens a new browser tab where you can enter your credentials to authenticate the desired org. Upon successful authentication the CLI automatically stores OAuth credentials on the local machine. Boom! Done.

With that, you can now run CLI commands against that org, like getting the list of all custom objects (snippet 1) or create a new record (snippet 2). The previously set alias “weather” makes it easy to select the org that we want to use.

➜ ~ sfdx force:schema:sobject:list -c custom -u weather
➜ ~ sfdx force:data:record:create -s Account -v "Name=SuperDemoCompany" -u weather
Successfully created record: 0010Y000013cwW5QAI.

There are many more commands available that you can run against any org, like running Apex tests, retrieving metadata or assigning permission sets. This is possible because the CLI uses the Tooling API or the Metadata API under the hood.

You can get the full list of available commands using sfdx force:doc:commands:list. I highly encourage you to check them out and use the CLI as much as possible. It makes you more productive because you don’t have to switch windows and contexts. We’re updating the CLI frequently, so bookmark and keep checking the CLI Release Notes.

Config files, scratch orgs, oh my!

One of the biggest features of Salesforce DX is the introduction of “scratch orgs.” These ephemeral orgs can (and should) be part of your development workflow. But before you can use this type of org you’ll have to set up a Salesforce DX project using the Salesforce CLI. Also, make sure that you have enabled the Dev Hub as explained in the previous post.

➜  sfdx force:project:create -n MyNewProject
target dir = /Users/rwinkelmeyer/blog
   create MyNewProject/sfdx-project.json
   create MyNewProject/
   create MyNewProject/.forceignore
   create MyNewProject/config/project-scratch-def.json

The sfdx force:project:create -n MyNewProject command (line 1) creates within the current folder a new subfolder with the name MyNewProject. It then automatically creates a couple of files and folders within that sub folder as you can see on the output (lines 2-6).

  • sfdx-project.json
  • config/project-scratch-def.json
  • .forceignore
  • force-app/main/default/aura (folder)

The sfdx-project.json file controls the main elements of the project and how the CLI behaves for this project (not globally, more on that later). The following snippet showcases the default contents for a new project definition file.

  "packageDirectories": [
      "path": "force-app",
      "default": true
  "namespace": "",
  "sfdcLoginUrl": "",
  "sourceApiVersion": "42.0"
  • packageDirectories: This parameter defines where the CLI should look for your source elements. This also means that you can change the location from force-app to something else if it’s more appropriate for your environment. You can add multiple directories here for a modular structure when you want to break up your app into multiple packages.
  • namespace: If your package uses a dedicated namespace you can (or should) configure it here.
  • sfdcLoginUrl: This parameter tells the CLI where it should point to for authentication. Why is this configurable? Well, you may want to connect to a sandbox and change the login url to
  • sourceApiVersion: The CLI has the capability of creating new code elements, like Apex classes, Lightning components etc. This parameter is used to automatically set the API version for the new components. The default value will match the API version of your Dev Hub org.

You find the full description of the configuration options here.

More file-based configuration

The second file type controls the “shape” of a newly created scratch org. In a new project an example configuration (project-scratch-def.json in the config folder) is created. You can have multiple configuration files, depending on your needs of developing and testing with different org preferences.

    "orgName": "rwinkelmeyer Company",
    "edition": "Developer",
    "orgPreferences" : {
        "enabled": ["S1DesktopEnabled"]

To use it with the CLI, you run the command (from the root folder of the project) sfdx force:org:create -f config/project-scratch-def.json -a MyScratchOrg.

This kind of configuration file allows for tremendous flexibility for testing your projects against a variety of orgs. For example, you could have a scratch org shape for an Enterprise org with Service Cloud licenses, with Lightning and multi-currency enabled, and another for an Unlimited org with Communities, Einstein Analytics and ETM 2.0 enabled. All of these JSON files can be bundled into the same projects for your team to use while building and testing. You can check out the documentation for scratch org shapes here. One of my personal highlights is the capability to disable the Lightning session cache in a scratch org by disabling the S1EncryptedStoragePref2 preference.

    "orgName": "rwinkelmeyer Company",
    "edition": "Developer",
    "orgPreferences" : {
        "enabled": ["S1DesktopEnabled"],
        "disabled": ["S1EncryptedStoragePref2"]

If you’re struggling to create a scratch org shape that perfectly mimics your production org, keep a couple things in mind:

First, your scratch org, even when ‘correctly’ configured, will not perfectly mirror production. You’ll still want a full copy sandbox if you need a mirror of production. Second, the usefulness of a scratch org isn’t tied to how perfectly it mirrors production (and the fact that it can be different can be very useful). Scratch orgs are there to help with the initial stages of development. If you want to test out impacts of turning on new features (like multi-currency or ETM 2.0), you can use a scratch org to do that. Once you’ve got the basics of what you’re building together in a scratch org, you’ll move changes into your regular app development lifecycle. Your sandboxes, which do more closely match production, can help you then perform more robust testing and refactoring before you release to production.

Think global, configure local

The Salesforce CLI relies on a couple of variables when communicating with the Salesforce org of your choice. You can set these variables globally (for all commands) or locally for a specific project. These variables can be the alias or the username (both are interchangeable) for your DevHub, the API version that the CLI should use, or the default username for your project.

➜ salesforce-einstein-platform-apex git:(develop) sfdx force:config:list
=== Config
NAME                  VALUE      LOCATION
───────────────────── ────────── ────────
apiVersion            42.0       Local
defaultdevhubusername DevHubDF17 Global
defaultusername       einstein1  Local

Per definition, local variables override global variables. Using the command sfdx force:config:set you can set project-specific variables. When you append the -g parameter you’ll set the value globally for all projects. Another example where the CLI is using a local configuration is the -s parameter in sfdx force:org:create. When you use this parameter, the command fetches the default username for the created scratch org (or the alias if you set one) and sets it as a local variable for the current project.

Push, change, pull, commit – even when it doesn’t work

One of my favorite features of the Salesforce CLI is to use it with scratch orgs for my daily development flow. These are the most common steps (which you likely already learned in the Trailhead module “App Development with Salesforce DX”).

  1. Create a new scratch org and push existing code to it (or start with a new project from scratch).
  2. Develop live in the scratch org and test changes.
  3. Pull the changes down to the local machine.
  4. Change some components locally.
  5. Push the components and validate the work in the org.
  6. Commit the changes to git (never forget that scratch orgs expire, so make sure your code is in version control).

We have modified the source representation for some of our more cumbersome Metadata API .xml files, such as custom object, static resources, and custom object translations. The force:source:push and force:source:pull commands work with a new org metadata tracking feature to help keep your project’s source in sync with the changes that you make in the scratch org. These changes make it easier for you to integrate with source control and manage your source files.

We all have been in the situation when something doesn’t work. Have you ever changed the same file locally and in the scratch org? What about changing a bunch of things in the org the day before and forgetting which files have been touched? That’s where sfdx force:source:status will be your savior.

➜ salesforce-einstein-platform-apex git:(develop) ✗ sfdx force:source:status -u einstein1
=== Source Status
STATE          FULL NAME                                                TYPE                 PROJECT PATH
────────────── ──────────────────────────────────────────────────────── ──────────────────── ────────────────────────────────────────────────────────────────────────────────────
Local Changed  EinsteinDatasetCreation/EinsteinDatasetCreationHelper.js AuraDefinitionBundle force-app/main/default/aura/EinsteinDatasetCreation/EinsteinDatasetCreationHelper.js
Local Changed  EinsteinModelSelect/EinsteinModelSelectController.js     AuraDefinitionBundle force-app/main/default/aura/EinsteinModelSelect/EinsteinModelSelectController.js
Remote Changed Einstein_PlaygroundController                            ApexClass            force-app/main/default/classes/Einstein_PlaygroundController.cls
Remote Changed Einstein_PlaygroundController                            ApexClass            force-app/main/default/classes/Einstein_PlaygroundController.cls-meta.xml
Remote Changed Einstein_PredictionService                               ApexClass            force-app/main/default/classes/Einstein_PredictionService.cls
Remote Changed Einstein_PredictionService                               ApexClass            force-app/main/default/classes/Einstein_PredictionService.cls-meta.xml

Once you run this command it’ll detect which files have changed (from a Salesforce DX perspective) since your last pull or push. This is extremely helpful for detecting remotely changed files, especially when you have changed the same file locally. The pull and push commands provide a —forceoverwrite flag which you can then use to enforce (as the name says) an overwrite for a changed file.

So what do you do when you have a remote file that you want to “reset” to the status of the local file, but the local file hasn’t changed? The Salesforce CLI uses a fingerprint hash to track local changes, so you have to change the content of the local file. Just change it a bit, like by adding a space somewhere, then run the push command and then undo the change. Done.

Last, but not least, you can exclude files from any of the force:source commands. The .forceignore file, which works like gits .gitignore, gives you granular control over that. Read more about how to use it here in the docs. And don’t forget to commit that file to version control, so that it is shared with your team.

The CLI is a powerful tool that simplifies your interaction with any Salesforce org. You can find numerous examples on how to use it with scratch orgs and source controlled code on this site, like in this blog post.

What’s next and what to do now

In the next post of this series we’ll dive into techniques for automating the CLI for an even better development workflow.

Also check out the Salesforce DX Trail on Trailhead to learn more about the standard flows when using the CLI like converting existing code, working with scratch orgs, or how to deploy metadata using the CLI.

You should also be sure to register for the upcoming Ask Me Anything (AMA) with the Salesforce DX Product Management team, coming up on February 27! If you’re looking to get more hands on with Salesforce DX at TrailheaDX, check out the Emerging Tech for Developers Bootcamp.

About the author

René Winkelmeyer works as Principal Developer Evangelist at Salesforce. He focuses on enterprise integrations, mobile, and security with the Salesforce Platform. You can follow him on Twitter @muenzpraeger.

Leave your comments...

Getting Started with Salesforce DX (Part 3 of 5)