GitHub Action
The sfdt repo ships a composite GitHub Action: run any sfdt command as a single uses: step.
The action sets up Node, installs the Salesforce CLI and a pinned @sfdt/cli, and
optionally authenticates the org.
- uses: scoobydrew83/sfdt@v0
with:
args-json: '["deploy","--smart","--org","ci","--dry-run"]'Inputs
| Input | Default | Description |
|---|---|---|
args-json | — | Preferred. The sfdt arguments as a JSON array of strings, executed with no shell — values can never be interpreted as shell syntax. Exactly one of args-json / command is required |
command | — | Deprecated — prefer args-json. The sfdt command line as one string. Without allow-shell-command it accepts only shell-neutral characters (letters, digits, space, _@%+=:,./-) and runs without a shell; quotes and metacharacters are rejected |
allow-shell-command | false | Set 'true' to run command through a shell (legacy eval) when it needs quoting or expansion. Only for fully trusted static strings — never workflow-interpolated user input. Removed in v1 |
cli-version | auto | auto installs the CLI version shipped at the pinned action ref — pinning the action tag pins the CLI. latest or an exact semver also accepted |
auth-method | none | none (job already authenticated) | sfdx-url | jwt |
sfdx-auth-url | — | Sfdx auth URL for sfdx-url (pass a secret) |
consumer-key | — | Connected-app consumer key (client id) for jwt (pass a secret) |
jwt-secret-key | — | Contents of the JWT signing private key for jwt (pass a secret), not a file path |
username | — | Username to authenticate as for jwt |
instance-url | https://login.salesforce.com | Login URL for jwt |
org-alias | ci-org | Alias assigned to the authenticated org (also set as default org) |
node-version | 22 | Node to set up — @sfdt/cli requires Node ≥ 22.15; the action fails fast with a clear ::error:: on older versions |
Example — PR validation (sfdx auth URL)
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Smart delta validation
uses: scoobydrew83/sfdt@v0
with:
args-json: '["deploy","--smart","--org","ci","--delta-base","origin/${{ github.event.pull_request.base.ref }}","--dry-run"]'
auth-method: sfdx-url
sfdx-auth-url: ${{ secrets.SFDX_AUTH_URL }}
org-alias: ciExample — JWT release deploy
- name: Smart delta deploy
uses: scoobydrew83/sfdt@v0
with:
args-json: '["deploy","--smart","--org","prod","--delta-base","v1.4.0","--notify"]'
auth-method: jwt
consumer-key: ${{ secrets.SFDX_CONSUMER_KEY }}
jwt-secret-key: ${{ secrets.SFDX_JWT_SECRET_KEY }}
username: ${{ secrets.SFDX_USERNAME }}
org-alias: prodMigrating from command
command still works for shell-neutral strings (it is now word-split without a shell), but
anything needing quotes or expansion must either move to args-json (recommended — each
argument becomes one array element, no quoting needed) or explicitly opt in with
allow-shell-command: 'true':
# before (ran through a shell — quoting-sensitive, injection-prone):
# command: deploy --smart --delta-base "origin/main" --dry-run
# after (no shell involved):
args-json: '["deploy","--smart","--delta-base","origin/main","--dry-run"]'Version pinning
The floating v0 tag tracks the newest stable release automatically (the release pipeline
force-moves it; beta releases never move it). Pin an exact tag (@v0.16.2) or a commit SHA for
immutable references — with cli-version: auto that also pins the CLI version.
Security
- Pass secrets only as
${{ secrets.X }}expressions — never as literals in the workflow file. - Prefer
args-json: its values are never interpreted by a shell. Never feed untrusted input (branch names, PR titles, issue text) tocommandwithallow-shell-command: 'true'— that mode runs the string through a shell. - The JWT key is written to a temp file and always deleted (trap on exit).
sfdt ci init --provider github --runner action generates complete workflows built on this
action — see CI/CD Integration.