CI/CD Integration
sfdt is built to run unattended. When stdin is not a TTY, commands run non-interactively —
confirmation prompts are skipped and the command exits with an appropriate code.
Non-interactive mode
- When
stdinisn’t a TTY, sfdt skips confirm prompts and proceeds with safe defaults. - The VS Code extension and other automated callers set
SFDT_NON_INTERACTIVE=trueso the CLI never hangs waiting for input. - Destructive data operations require an explicit flag non-interactively (e.g.
sfdt data delete <set> --yes).
Exit codes
All sfdt commands exit 0 on success and 1 on failure. Gate subsequent steps with set -e or
by checking $?. sfdt changelog check exits 1 when the changelog needs updating, so it works
as a pre-merge gate.
JSON output
Most operational commands accept --json for machine-readable output — audit, monitor,
drift, rollback, doctor, scan, data, docs, flow, scratch, feature-flags,
extension status, and others.
--json emits a Salesforce-native envelope on stdout, matching the shape sf itself uses so
sfdt composes in the same pipelines:
// success
{
"status": 0, // numeric exit code (0 = success)
"result": { /* … */ }, // the command payload
"warnings": []
}
// failure
{
"status": 1, // exit code: 1 general, 2 config, 3 connectivity
"name": "Error",
"message": "…",
"exitCode": 1,
"warnings": []
}Pipe it into jq and read the payload from .result:
sfdt drift --json | jq '.result.driftStatus'
sfdt audit --json | jq '.status' # 0 when healthyThe envelope is a stdout contract. The on-disk snapshot files some commands also write
(logs/audit-latest.json, logs/monitor-latest.json, …) keep their raw payload shape — they are
the result payload without the envelope wrapper.
Generating a pipeline — sfdt ci init
sfdt ci init generates a ready-to-run pipeline for your Git provider — four workflow types:
scheduled org monitoring, PR smart-deploy validation, approval-gated release
deploys, and scratch-org CI.
sfdt ci init --provider github --type monitor --cron "0 6 * * *"
sfdt ci init --provider gitlab --type deploy --org uat --auth jwt
sfdt ci init --provider github --type release --branch main --environment production
sfdt ci init --provider azure --type scratch --org my-devhub
sfdt ci init --provider github --type deploy --runner action # one-step workflows via the GitHub Action
sfdt ci init --provider bitbucket --type deploy --print # print instead of writesfdt monitor schedule --provider github is a thin alias for ci init --type monitor (it also
accepts --auth and --runner).
| Flag | Default | Notes |
|---|---|---|
--provider <name> | (required) | github | gitlab | azure | bitbucket |
--type <type> | monitor | monitor (scheduled health) | deploy (PR validation) | release (real deploy) | scratch (scratch-org CI) |
--auth <method> | config ci.authMethod or sfdx-url | sfdx-url | jwt — swaps the generated auth steps and the documented secrets |
--runner <name> | config ci.runner or npx | npx | docker | action. docker (GitLab/Bitbucket only): job runs on ghcr.io/scoobydrew83/sfdt:latest — no per-run CLI installs. action (GitHub only; monitor/deploy/release): one uses: scoobydrew83/sfdt@v0 step replaces setup/auth — see GitHub Action. Scratch is excluded from action because it drives raw sf commands with their own always-run cleanup |
--cron <expr> | 0 6 * * * | Schedule for monitor workflows |
--org <alias> | config.defaultOrg | Target org alias embedded in the workflow. For --type scratch this is the Dev Hub alias |
--branch <name> | config defaultBranch or main | Protected branch that triggers a release workflow |
--environment <name> | config ci.environment or production | Approval environment for release workflows |
--delta-base <ref> | deploy: main; release: HEAD~1 | Base ref for the smart-deploy delta. Release templates resolve the last git tag at runtime (git describe); this flag is only the no-tags fallback |
--definition-file <path> | config scratch.definitionFile or config/project-scratch-def.json | Scratch-org definition (scratch type) |
--node <version> | 22 | Node.js version for the CI runner — @sfdt/cli requires Node ≥ 22.15 |
--out | provider convention | Output path override |
--print | — | Print to stdout instead of writing |
--force | — | Overwrite an existing file |
--json | — | Machine-readable result |
Workflow types
monitor— scheduled org health: runssfdt monitor all --notify --jsonon the--cronschedule. See Notifications.deploy— PR validation: runs an advisorysfdt qualityscan, thensfdt deploy --smart --dry-runagainst the PR’s base branch (full git history is fetched so the delta can be computed). On GitHub the quality scan emits SARIF and uploads it to the Security → Code scanning tab (the generated workflow includespermissions: security-events: write). The scan is non-blocking by design. See Smart Deploy andsfdt quality.release— approval-gated real deploy from the protected--branch: deploys the delta since the last git tag withdeploy --smart --notify. Tag each release (e.g.sfdt release <version>orsfdt deploy --tag) so the delta stays small.scratch— scratch-org CI: creates a 1-day scratch org from the Dev Hub, deploys source, runsRunLocalTestswith coverage, optionally runs LWC Jest tests (the step is emitted commented-out unless a Jest setup is detected in the project), and always deletes the org (if: always()/after_script/condition: always()/ after-script, per provider).
Release approval gating, per provider
- GitHub — the job runs in a GitHub Environment: add required reviewers under Settings → Environments, and scope the auth secret to the environment.
- GitLab — the job is
when: manualand bound to an environment: protect it via Settings → CI/CD → Protected environments. - Azure — uses a
deployment:job bound to an Environment: create it first (Pipelines → Environments) and add “Approvals and checks”; plain script jobs do not honor approvals. - Bitbucket — the step is bound to a deployment environment; restricting deployers requires
Premium. The template header shows a manual-trigger
custom:pipeline alternative for other plans.
Where files land
- GitHub →
.github/workflows/(e.g.sfdt-monitor.yml,sfdt-deploy.yml) - GitLab / Azure / Bitbucket → a standalone fragment under
.sfdt/ci/for you to merge/include (these providers use a single top-level pipeline file).
Required secrets
Secrets depend on --auth (they’re listed in the generated file’s header and the post-generate
hint):
sfdx-url—SFDX_AUTH_URL(fromsf org auth show-sfdx-auth-url)jwt—SFDX_CONSUMER_KEY,SFDX_JWT_SECRET_KEY(the key contents, not a path),SFDX_USERNAME, optionalSFDX_INSTANCE_URL- optional for monitor/release:
SLACK_WEBHOOK_URL,TEAMS_WEBHOOK_URL
Add them in your provider’s secret store:
- GitHub → Settings → Secrets and variables → Actions
- GitLab → Settings → CI/CD → Variables (masked)
- Azure → Pipelines → Library / pipeline variables (secret)
- Bitbucket → Repository settings → Pipelines → Variables (secured)
For the full auth setup — retrieving the auth URL, creating the JWT connected app and certificate, multiline-key handling, rotation, and troubleshooting — see CI Authentication.
Defaults for --auth, --runner, and --environment can be set once in the
ci config block — flags override config; config overrides built-in
defaults.
GitHub Actions
For a single-step setup, use the official GitHub Action —
uses: scoobydrew83/sfdt@v0 handles Node, the CLI install, and org auth. Or script it yourself:
- name: Preflight and deploy
run: |
sfdt preflight
sfdt test
sfdt deploy --skip-preflight
env:
SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL }}Explain failures automatically:
- name: Explain failure
if: failure()
run: sfdt explain --latestFor AI commands in CI, install and authenticate Gemini or OpenAI before running sfdt. Claude requires an interactive session and isn’t suitable for CI. See AI Providers.
Docker
For a self-contained toolchain (Node + sf CLI + git/jq/bash), run sfdt from the official Docker
image — see Docker.