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:
- Executable and version: Is the runner using global
sffrom the Salesforce CLI installers or a pinned npm@salesforce/cli? Mismatched versions explain flag drift between laptop and CI. - Auth model: Interactive
sf org login webcannot complete in headless automation—CI needs JWT or a pre-provisioned auth file on the runner. Know which org alias each step must target. - Output contract: Does a downstream step parse JSON with
jq, archive JUnit, or only need a pass/fail exit code? That determines--json,--result-format, and whether you must scrape job IDs from async responses. - Most common wrong assumption: That a zero exit code always means “deploy/tests fully succeeded” without
--waitor without reading JSON status fields—many flows enqueue work and return before completion unless configured otherwise. - Limits: Platform test timeouts, concurrent deployment limits, and data API bulk thresholds still apply; the CLI surfaces errors, but automation must retry and backoff responsibly.
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