Getting Started with Salesforce DX (Part 4 of 5)

This is the fourth 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.

Why scripting?

Shoshana Zuboff stated more than 30 years ago: “Everything that can be automated will be automated”.

While you and your team of colleagues are getting comfortable with the plethora of options, as outlined in the previous post, it’s also the perfect timing to think about automating steps using scripts.

Scripting the CLI helps you. It helps you to effectively use the CLI commands (read: type less). It helps you to avoid manual errors when entering commands and parameters. It helps you and your team to use the same routines by committing the scripts to source as parts of your project. It helps you to be prepared for implementing a Continuous Development strategy. So let’s dive into some options that you can implement yourself.

Start simple with aliases and functions

Every operating system has the capability of creating aliases for commands, be it for example via the .bash_profile on Mac/Unix or Doskey/AddConsoleAlias on Windows. Aliases are not really scripts, but they simplify your CLI experience by reducing the amount of needed keystrokes.

Check out this excerpt of some aliases that I use every day:

alias spush='sfdx force:source:push'
alias spull='sfdx force:source:pull'
alias screate='sfdx force:org:create -s -f config/project-scratch-def.json -a '
alias screatepush='sfdx force:org:create -s -f config/project-scratch-def.json && spush'

With a short screate myAlias I create a new scratch org with the given scratch org alias as a parameter (line 3).

Aliases are good for simple commands but certainly not the ideal solution for handling parameters. That’s where custom functions come in handy.

sproject() {
    sfdx force:project:create -n $1
    cd $1
    sfdx force:org:create -s -f config/project-scratch-def.json -a $1 -d $2

By adding this custom function to my .bash_profile I can now run sproject myproject 2 and it automatically creates a new Salesforce DX project. Then it spins up a new scratch org with the alias “myproject” and an org duration of two days. Neat, right?

Functions are just scripts, so you can do things like gather user input, as you see in this extended example:

sproject() {
    # Check if any arguments are passed
    if [ "$#" = 0]; then
        echo "Error: Specify the project name as parameter"
        return 1
    # Create the new Salesforce DX project
    echo "Creating new project directory $1"
    sfdx force:project:create -n $1
    # Change to the directory of the project
    cd $1
    if [ "$#" = 1]; then
        echo "You didn't pass the number of duration days. Please specify (default $DAYS, min 1, max 30):"
        read input_days
        if [ -n "$input_days" ]; then
    echo "Creating the new scratch org"
    sfdx force:org:create -s -f config/project-scratch-def.json -a $1 -d $DAYS

Besides validating that all needed parameters are provided, you can also interactively ask the user for data (line 15). No need to remember the exact parameter names or value limits.

Build your project scripts

Every developer has likely been in the situation of working on an older project and trying to work her or his way through setup instructions: import test data, create users, assign permission sets and more. That is why README files get really long! While repetition is good for your muscle memory, it can also be tedious and error-prone. A typo here, an incomplete instruction there— we all know how that ends.

So why not put all the needed commands into a single batch file?

sfdx force:org:create -s -f config/project-scratch-def.json -a tedious-setup
sfdx force:source:push
sfdx force:user:create firstname=Test lastname=User -a testuser -f config/project-user-def.json
sfdx force:data:tree:import —plan ./data/Account-plan.json
sfdx force:data:tree:import —plan ./data/Car__c-plan.json
sfdx force:data:record:create -s Rental__c -v "Name='Universal Rental'"
sfdx force:user:permset:assign -n Cool_Lighting_App
sfdx force:user:permset:assign -o testuser -n Cool_Lighting_App
sfdx force:user:password:generate -u testuser
sfdx force:user:display -u testuser
sfdx force:org:open -p /one/

This example:

  • Creates a new scratch org
  • Pushes the source
  • Creates an additional user
  • Imports data
  • Creates a new record
  • Assigns permission sets
  • Generates a password for the additional user
  • Opens the scratch org, with ‘MyCoolLightningApp’ already selected

Put all this in a script like (or orgSetup.cmd for Windows users), add that script to your source repo and you’re set for future setups. No more manual typing (<insert happy dance here>).

And you shouldn’t stop there. Imagine a deploySandbox script for converting the source to MDAPI format or creating a package and deploying it to a sandbox for testing purposes. Or scripts for running specific Apex or UI tests on demand. Whenever there is a need to run multiple commands, or sometimes only a single command, more often than once you should script it – seriously.

Reusing JSON command output

Every Salesforce CLI command provides a —json flag. By using this flag you get access to the full JSON response, compared to a human-readable response. The following snippet shows the standard output that you’re familiar with when creating a new scratch org.

➜ salesforce-einstein-platform-apex git:(develop) sfdx force:org:create -a einstein3 -s -f config/project-scratch-def.json
Successfully created scratch org: 00D3B0000000ZjtUAE, username:

We’re adding in the next snippet the —json flag. As you see you’ll get the raw JSON data response.

➜ salesforce-einstein-platform-apex git:(develop) sfdx force:org:create -a einstein4 -s -f config/project-scratch-def.json --json

With a tool like the command-line JSON processor jq you can pipe and parse the output, like directly extracting the orgId from the response.

➜ salesforce-einstein-platform-apex git:(develop) sfdx force:org:create -a einstein5 -s -f config/project-scratch-def.json --json | jq '.result.orgId'

This functionality comes in very handy when you want to pass down output from a command to another command. The next snippet shows how to extract the username of a new created user, store it in a local variable and then reuse it in another command.

VARUSER=$(sfdx force:user:create -u einstein5 --json | jq '.result.fields.username')
sfdx force:user:permset:assign -o $VARUSER -n Einstein_Platform_Playground

While this is a rather basic example it shows the power of the —json flag and how you can use it. On a side note it’s worth mentioning that the sfdx force:user:create command provides a flag for defining a static alias for the new user, so you should probably use that.

Did someone say Apex?

While the CLI has many powerful options, it cannot do everything like complex data operations or creating ContentVersion records. That’s where an often overlooked feature can help you – the execution of Anonymous Apex using the CLI.


# Simple shell script to automatically set up Einstein Platform

if [ "$#" -eq 3 ]; then
  DEFAULTORG="-u $3"


echo "Einstein Platform User: $USERNAME" 
echo "Einstein Platform File: $FILE"
echo ""
echo "Creating Apex file"

echo "Einstein_Settings__c setting = new Einstein_Settings__c(Einstein_Email__c='$USERNAME');" > $_APEX_OUTPUT
echo "insert setting;" >> $_APEX_OUTPUT
echo "public String key = '';" >> $_APEX_OUTPUT

while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "key = key + '$line\n';" >> $_APEX_OUTPUT;
done < "$FILE"

echo "ContentVersion cont = new ContentVersion();" >> $_APEX_OUTPUT
echo "cont.Title = 'einstein_platform';" >> $_APEX_OUTPUT
echo "cont.PathOnClient = 'einstein_platform.pem';" >> $_APEX_OUTPUT
echo "cont.VersionData = Blob.valueOf(key);" >> $_APEX_OUTPUT
echo "insert cont;" >> $_APEX_OUTPUT

echo ""
echo "Executing anonymous Apex via sfdx CLI"

sfdx force:apex:execute -f $_APEX_OUTPUT $DEFAULTORG

echo "Apex executed"


echo "Done!"

This script generates a local file with Apex code which then gets executed via the Salesforce CLI. Magic happens on line 23, where the script reads a local file and then creates a new ContentVersion record in the scratch org with the contents of that file.

Why is this great? Instead of manually uploading the file (Don’t forget to check for the right title!) on every new scratch org it just takes a one-liner to call this script from the local machine. And you can call it from an orgSetup script as mentioned before to simplify the experience even more.

So why not take your post-install scripts and commit them to your source repo? Your team will thank you.

To sum it up…

These examples are only a few out of many options. But they showcase how you can gain productivity by automating everyday tasks. Just think about the additional productivity you’ll get when multiplying this by every member on your development team!

How should you start? It’s simple.

  • Monitor yourself to see which repetitive commands you execute often.
  • Then decide if you want to have them as aliases, functions or scripts. This can and will evolve over time.
  • When you set up your next source repo, record the commands and add them in a script to the repo— the sooner, the better.

What’s next and what to do now

Check out the CLI reference to get familiar with the available commands to learn what’s possible with the CLI. The App Development with Salesforce DX Trailhead module contains great instructions for your first steps. I also encourage you to visit my favourite sites for starting with shell scripting or Windows scripting. With all that knowledge you’ll be set up for the fifth part of this series – implementing Continuous Development with Salesforce DX.

For additional ideas on how to start your Salesforce CLI automation journey, I highly recommend these recordings:

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! And 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 4 of 5)