SfSkills Domains Docs GitHub

Home / devops / salesforce-cli-automation

salesforce-cli-automation

Automating Salesforce work with the unified Salesforce CLI (`sf`, v2): shell scripts, Make/npm tasks, cron jobs and CI steps needing stable flags, `--json` output, org aliases, bulk/data commands, plugins, non-interactive auth. Trigger keywords: sf CLI automation, sfdx migration, JSON output CI, sf project deploy script, sf data bulk, CLI plugins, target-org alias, machine-readable CLI. NOT for basic sf commands, org auth or first DX project setup — use apex/sf-cli-and-sfdx-essentials. NOT for writing your own sf plugin — use devops/sf-cli-plugin-authoring.

Open the skill folder on GitHub


Salesforce CLI Automation

This skill activates when the task is to design, refactor, or troubleshoot automation that drives Salesforce through the CLI—not when the primary question is “which CI YAML product to use,” but when the core problem is how sf behaves under scripts, how to get stable machine-readable output, and how to avoid the sharp edges of async commands, org selection, and deprecated sfdx syntax. Official behavior, flags, and command families are defined in the Salesforce CLI Reference; project and auth models are covered in the Salesforce DX Developer Guide; metadata semantics that the CLI ultimately invokes belong in the Metadata API Developer Guide.

The Salesforce CLI v2 exposes capabilities under the sf executable. Legacy sfdx force:* style commands remain for compatibility in many environments, but new investment and features ship in sf, and automation should standardize on sf so scripts do not break when legacy paths narrow. Treat the CLI as a contract with stdout/stderr and exit codes: in CI, prefer --json on commands that support it when you parse results, and pair long-running operations with --wait or explicit async job polling (--async plus follow-up commands) so pipelines do not finish “green” while work is still running server-side.

Org targeting should always be explicit in shared automation: use --target-org <alias> (or the supported environment variables your runner sets) so a developer laptop default alias cannot silently change production. Plugins extend sf; pin versions when a script depends on a plugin command, because plugin updates can change flags or output shape. For data at scale, favor the families documented under data commands (sf data tree vs bulk paths per the CLI reference) instead of ad-hoc REST scripts that duplicate CLI-tested flows.

This skill complements platform-specific CI skills (which show where to store secrets and how to structure jobs) and interactive CLI essentials (day-to-day auth and scratch org creation). Here the focus stays on portable CLI automation patterns any environment can reuse.


Before Starting

Gather this context before working on anything in this domain:


Core Concepts

Stable, parseable CLI output

Human tables and spinners are convenient locally but brittle in logs. For automation, prefer --json where the command supports it so your script inspects structured status, result, and error arrays instead of regexing prose. When a command offers --result-format (for example Apex tests), pick json or junit to match the consumer. Keep SF_USE_PROGRESS_BAR=false (or equivalent environment conventions your org standardizes) so progress rendering does not corrupt captured output in CI logs.

Synchronous completion vs async job IDs

Deploy and test commands can return before server-side work completes unless you set --wait with an adequate timeout or deliberately run --async and poll with the companion “report” or “resume” style commands documented in the CLI reference for that command family. Scripts that omit waiting are a frequent source of flaky pipelines that merge broken metadata because validation had not finished.

Org aliases, default orgs, and CI isolation

Automation must not rely on whatever sf config get target-org returns on a shared runner. Pass --target-org on every mutating command, derive dynamic values (instance URL, org id) with sf org display --json when needed, and avoid storing long-lived refresh tokens in repo files—use the secret mechanism of the hosting platform, surfaced to the process environment for sf org login jwt or similar non-interactive flows described in the Salesforce DX Developer Guide.


Common Patterns

JSON gate wrapper

When to use: A shell script must fail the build if a deploy or test command reports failure in JSON even when stderr looks clean.

How it works: Run sf <command> ... --json, capture stdout, parse with python3 -c or jq to check top-level status (and nested result.success where applicable), and exit non-zero on failure. Keep CLI stderr visible for supportability.

Why not the alternative: Grepping for the word Error in human output breaks on localized CLI messages and minor formatting changes.

Replace legacy sfdx force:* in scripts

When to use: Maintenance on older bash or npm scripts still calling sfdx force:source:deploy or similar.

How it works: Map to sf project deploy start, sf project retrieve start, sf apex run test, and other unified topics listed in the CLI reference migration sections. Install only the modern CLI toolchain on runners to avoid conflicting executables.

Why not the alternative: Keeping sfdx indefinitely stores technical debt and duplicates auth/config behavior between two entry points.


The sf data Command Families

Automation that moves records instead of metadata lives under the sf data topic. Picking the wrong family is a common footgun: single-record commands do not scale to volume, and bulk commands are overkill (and slower to spin up a job) for one row. The topic breaks into four families, all defined in the 1 command reference.

FamilyCommandsShapeWhen to use
Single-record CRUDsf data create record, sf data get record, sf data update record, sf data delete record, sf data create fileOne record (or one file upload, e.g. a ContentVersion) against a Salesforce or Tooling API objectSmoke-test steps, one-off fixups, uploading a single asset—not loops over many rows
Ad-hoc query / searchsf data query (SOQL), sf data search (SOSL)Read-only; pairs with --json to feed downstream stepsDeriving IDs, counts, or gate conditions inside a script
Tree JSON import/exportsf data export tree, sf data import treeOne or more JSON files carrying related recordsSeeding a scratch org or dev sandbox with a small, relationship-aware fixture set
Bulk API 2.0 (CSV)sf data export bulk, sf data import bulk, sf data update bulk, sf data upsert bulk, sf data delete bulkCSV files in and out; each verb runs a Bulk API 2.0 jobVolume data movement between orgs where record-by-record REST would blow limits

Bulk jobs are asynchronous by contract. Every bulk verb has a matching ... resume command (sf data import resume, sf data upsert resume, and so on) that reattaches to a job you already started by its job ID, so a script can start a load, exit the step, and poll status later. sf data resume reports the status of a running bulk job or batch, and sf data bulk results retrieves the success/failure detail of an ingest job you ran earlier. This is the same async discipline the rest of this skill applies to deploys and tests: either wait long enough for the job to finish or capture the job ID from --json and poll with the companion resume/results command before asserting success. Do not treat a returned prompt as a completed load.

Legacy note: the predecessor sfdx force:data:* family is deprecated in favor of this unified sf data topic (deprecated 2 reference). Treat any force:data:tree:import or force:data:bulk:upsert you find in older scripts the same way you treat force:source:deploy—map it to the sf data equivalent.


Decision Guidance

SituationRecommended ApproachReason
Need structured output for toolingAdd --json (and parse explicitly)Stable schema-oriented integration per CLI reference
Long deploy or full test suite in CIUse --wait with a timeout above peak runtimePrevents returning before completion
Very large data movement between orgsUse the sf data ... bulk verbs (Bulk API 2.0, CSV) and poll with resume/bulk resultsDesigned for volume instead of record-by-record scripts
Seeding a scratch org with related fixture recordsUse sf data export tree / sf data import tree (JSON)Preserves relationships in a small, versionable fixture set
A single record or file to create/read/fixUse sf data create/get/update/delete record or sf data create fileAvoids spinning up a bulk job for one row
Developer-only convenience scriptHuman output acceptable; still pin --target-orgReduces accidental cross-org execution

  1. Confirm the automation environment: sf version, plugins, headless vs interactive, and which org aliases exist on the runner.
  2. Identify the operation family (project deploy, Apex test, data, org login) in the Salesforce CLI Reference and list required flags: --target-org, --json or --result-format, and wait/async behavior.
  3. Rewrite or validate commands to avoid legacy sfdx force:* namespaces unless a documented exception applies.
  4. Add explicit completion handling (--wait or async polling) and assert success using structured output, not log scraping.
  5. Run the skill’s check_salesforce_cli_automation.py against the repository root to catch common automation footguns in CI and shell files.
  6. Document the final command snippets and environment variables for operators who will extend the script later.

Review Checklist

Run through these before marking work in this area complete:


Salesforce-Specific Gotchas

  1. Silent async returns — Some commands enqueue work and can return before the org finishes processing unless --wait is set appropriately; pipelines may pass while deployments or tests are still running or failing afterward.
  2. Default org leakage — Omitting --target-org on shared runners can apply metadata to whatever alias happens to be authorized last, which is a severe reliability and security risk.
  3. Output format drift — Parsing human-formatted tables breaks across CLI versions; --json fields are the supported integration surface where available.

Output Artifacts

ArtifactDescription
sf command snippetsCopy-pasteable commands with automation-oriented flags for scripts and jobs
JSON parsing notesWhich top-level keys to assert after --json for the command families in use
Migration notesLegacy sfdx → sf mapping relevant to the scripts under review

Dependencies

Package files