Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

In one sentence: there are about twelve commands you will use daily and several hundred you will not. This is the working set, in the sf syntax that replaced sfdx, with the flags that matter and the two or three places the behaviour surprises people.

The commands you will actually useAbout twelve commands cover the working day; the other several hundred can wait.01sf org login web --alias uat --instance-url https://test.salesforce.com02sf project deploy start --source-dir force-app --dry-run — validates forfree, so never skip it03sf project deploy start --test-level RunLocalTests04sf apex tail log --color, which beats refreshing the Developer Console05sf data query --query "..." --result-format csv > out.csv06--json on anything a script reads. The human format is not stable and mustnever be parsedSetting a default org with sf config set is convenient and is also how people deploy to thewrong org. On anything touching production, pass --target-org explicitly.
The working set: about twelve commands and the flags that matter.

Getting connected

sf org login web --alias prod --instance-url https://login.salesforce.com
sf org login web --alias uat  --instance-url https://test.salesforce.com
sf org list
sf config set target-org=uat

Aliases are the point. Once an org has an alias you stop typing usernames, and --target-org takes the alias everywhere. Setting a default with sf config set is convenient and is also how people deploy to the wrong org, so on anything touching production, pass --target-org explicitly and accept the extra typing.

Getting metadata in and out

sf project retrieve start --metadata ApexClass:AccountService
sf project retrieve start --metadata "Flow,PermissionSet"
sf project deploy start --source-dir force-app --dry-run
sf project deploy start --source-dir force-app --test-level RunLocalTests

Two things to know. --dry-run validates without deploying and is free, so there is no reason to skip it. And the metadata type names are case-sensitive and specific (ApexClass, not apexclass or Apex Class) with the full list available from sf org list metadata-types.

For a scratch org or a sandbox with source tracking enabled, sf project deploy start with no arguments deploys only what changed, and sf project retrieve start with no arguments pulls what changed in the org. That is the loop you will actually spend your day in.

Running things in the org

sf apex run --file scripts/fix-orphans.apex
sf apex run test --tests AccountServiceTest --result-format human --wait 10
sf apex get log --number 5
sf apex tail log --color

sf apex tail log is the one people discover late. It streams debug logs to your terminal as they happen, which beats refreshing the Developer Console by a wide margin. It needs an active trace flag, which the command sets up for you when you agree to the prompt.

Querying and moving data

sf data query --query "SELECT Id, Name FROM Account LIMIT 5"
sf data query --query "SELECT Id FROM Contact WHERE Email = null" --result-format csv > orphans.csv
sf data import tree --plan data/sample-plan.json
sf data delete record --sobject Account --record-id 001...

--result-format csv plus a redirect is how you get a file without opening a browser. For bulk work, sf data import bulk and sf data export bulk handle the job creation and polling, which removes the main reason people opened Data Loader.

Scratch orgs

sf org create scratch --definition-file config/project-scratch-def.json \
  --alias dev1 --duration-days 7 --set-default
sf org open --target-org dev1
sf org open --target-org dev1 --url-only
sf org delete scratch --target-org dev1

--url-only prints a front-door URL instead of opening a browser, which is what you want when a script or a headless browser needs the session rather than you.

The flags worth memorising

  • --json on any command, for parsing the output with jq in a script. The human format is not stable and should never be parsed.
  • --dry-run on deploy, always.
  • --wait N on anything asynchronous, or the command returns before the work finishes and your script continues into a race.
  • --test-level on deploy: NoTestRun, RunSpecifiedTests, RunLocalTests, RunAllTestsInOrg. Production deployments require tests regardless of what you pass.

Where it surprises people

Deploy order. The CLI does not sort your metadata for you in every case, which is one reason the deployment option you chose shows up as a pipeline problem rather than a tooling one. A permission set referencing a field that is in the same deployment usually works. One referencing a field that is not deployed yet does not.

Source tracking is not universal. Scratch orgs and sandboxes created with tracking enabled have it. A production org does not, so deploy start with no arguments there does nothing useful and you must name what you are deploying.

The old sfdx commands still run and print a deprecation note. They are aliases, and mixing the two styles in a script is how a pipeline breaks when the aliases are eventually removed.

Frequently asked

How do I see what changed before deploying? sf project deploy preview lists what would go, based on source tracking.

Why does my deployment say “no changes to deploy”? Source tracking thinks the org already matches. sf project reset tracking clears that state, and then everything is a change again.

Can I deploy a single file? Yes: --source-dir takes a path to one file. The CLI adds the associated metadata for you.