Get your Salesforce CI/CD pipeline running in under 15 minutes!
A complete CI/CD pipeline with:
- ✅ Automated setup of Salesforce CLI
- ✅ Cross-platform compatibility (Linux, Windows, macOS)
- ✅ Code quality and testing tools included
- GitHub repository with Salesforce code
- Salesforce org(s) with Connected App
- JWT private key for authentication
Required for all orgs: As of the Winter '25 release (October 2024), Salesforce recommends using External Client Apps for CI/CD integrations.
- Setup → Quick Find → Search "External Client Apps"
- Click Settings (in External Client Apps section)
- Toggle ON the setting "Enable External Client Apps"
- Click Save
Note: This is a one-time org-level setting that enables the External Client Apps feature.
Generate your certificate and private key before creating the External Client App:
# Generate private key
openssl genrsa -out server.key 2048
# Generate certificate
openssl req -new -x509 -key server.key -out server.crt -days 3650
# When prompted, you can use any values for the certificate fields
# Common Name, Organization, etc. - these don't affect functionalityYou now have:
server.crt- Certificate to upload to Salesforceserver.key- Private key to store as GitHub Secret
-
Setup → Quick Find → Search "External Client Apps"
-
Click New
-
Fill in the basic information:
- External Client App Name:
GitHub Actions CI - Contact Email: your@email.com
- Description (optional):
JWT authentication for GitHub Actions
- External Client App Name:
-
Configure OAuth Settings:
- ✅ Check "Enable OAuth Settings"
- Callback URL:
http://localhost:1717/OauthRedirect- Note: This URL is not used for JWT auth but is required by Salesforce
-
Enable JWT Bearer Flow:
- ✅ Check "Use digital signatures"
- Click Choose File and upload your
server.crtcertificate
-
Select OAuth Scopes:
- Add these scopes from "Available OAuth Scopes" to "Selected OAuth Scopes":
- ✅ Access and manage your data (api)
- ✅ Perform requests on your behalf at any time (refresh_token, offline_access)
- Add these scopes from "Available OAuth Scopes" to "Selected OAuth Scopes":
-
Click Save
-
Note your Consumer Key:
- After saving, you'll see the Consumer Key (also called Client ID)
- Copy this - you'll need it for GitHub Secrets
- The Consumer Secret is not needed for JWT authentication
-
Wait 2-10 minutes for the External Client App to propagate
To avoid manual authorization:
- Return to your External Client App
- Click Manage
- Click Edit Policies
- Under OAuth Policies:
- Permitted Users: Select "All users may self-authorize"
- IP Relaxation: Select "Relax IP restrictions" (for GitHub Actions)
- Click Save
If your org is on a pre-Winter '25 release, or you prefer the traditional approach, you can still use Connected Apps:
Click to expand: Connected App Instructions (Legacy)
- Setup → App Manager → New Connected App
- Fill in basic info:
- Connected App Name:
GitHub Actions CI - Contact Email: your@email.com
- Connected App Name:
- Enable OAuth Settings:
- ✅ Enable OAuth Settings
- Callback URL:
http://localhost:1717/OauthRedirect - ✅ Use digital signatures
- Upload your
server.crtcertificate - OAuth Scopes: Access and manage your data (api), Perform requests on your behalf at any time (refresh_token, offline_access)
- Save and note the Consumer Key (Client ID)
- Wait 2-10 minutes for propagation
Note: Connected Apps and External Client Apps both work identically with this GitHub Action. The authentication flow and credentials are the same.
SFDX_JWT_KEY → Contents of server.key file
SFDX_CLIENT_ID → Consumer Key from External Client App
How to get the JWT key contents:
# View the entire private key
cat server.key
# Copy everything including the header and footer:
# -----BEGIN RSA PRIVATE KEY-----
# [multiple lines of encrypted key]
# -----END RSA PRIVATE KEY-----Important: Copy the entire contents of server.key including the BEGIN and END lines.
SFDX_USERNAME → Your Salesforce username (the org you authenticated)
For multiple environments (optional):
PROD_USERNAME → Production org username
UAT_USERNAME → UAT org username
INT_USERNAME → Integration org username
After creating your External Client App and secrets, verify the setup works:
# Test JWT authentication with your new External Client App
sf org login jwt \
--client-id YOUR_CONSUMER_KEY \
--jwt-key-file server.key \
--username your@email.com \
--instance-url https://login.salesforce.com
# If successful, you'll see:
# Successfully authorized your@email.com with org ID 00D...If this works locally, it will work in GitHub Actions!
- Never commit
server.keyto your repository - Add to .gitignore:
echo "server.key" >> .gitignore echo "server.crt" >> .gitignore
- Store backups of your certificate and key in a secure password manager
- Rotate keys annually or when team members leave
If authentication fails:
- ✅ Verify
server.crtwas uploaded to the External Client App - ✅ Ensure the entire
server.keycontent is inSFDX_JWT_KEYsecret - ✅ Check that Consumer Key matches
SFDX_CLIENT_ID - ✅ Confirm username is correct
- ✅ Wait the full 10 minutes after creating the External Client App
- ✅ For sandboxes, use
--instance-url https://test.salesforce.com
You can use the same External Client App and certificate across multiple orgs:
- Create the External Client App in each org (upload same
server.crt) - Use the same
SFDX_JWT_KEYsecret - Create separate secrets/variables for each org's Consumer Key and Username:
PROD_CLIENT_ID / PROD_USERNAME UAT_CLIENT_ID / UAT_USERNAME INT_CLIENT_ID / INT_USERNAME
Or use one External Client App in production and authenticate to other orgs using the same credentials (recommended for simplicity).
mkdir -p .github/workflowsCreate .github/workflows/test-salesforce.yml with the following content:
name: Validate Salesforce
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Salesforce
uses: rdbumstead/setup-salesforce-action@v2
with:
jwt_key: ${{ secrets.SFDX_JWT_KEY }}
client_id: ${{ secrets.SFDX_CLIENT_ID }}
username: ${{ vars.SFDX_USERNAME }}
install_scanner: "true"
install_prettier: "true"
- name: Run Tests
run: sf project deploy validate --source-dir force-app# Create a test branch
git checkout -b test-cicd
# Make a small change
echo "// Test change" >> force-app/main/default/classes/SomeClass.cls
# Commit and push
git add .
git commit -m "test: CI/CD pipeline"
git push origin test-cicd- Go to GitHub → Pull Requests → New Pull Request
- Base:
main(or your default branch) - Compare:
test-cicd - Create Pull Request
You should see:
- ✅ Salesforce CLI setup
- ✅ Authentication success
- ✅ Verification command running
Your Salesforce CI/CD pipeline is now active!
If your Salesforce code isn't in force-app:
- uses: rdbumstead/setup-salesforce-action@v2
with:
source_dirs: "src,packages/core"If you need custom SF CLI plugins:
- uses: rdbumstead/setup-salesforce-action@v2
with:
custom_sf_plugins: "sfdx-hardis,your-plugin"Don't want to set up JWT certificates? Use SFDX Auth URL instead:
- name: Setup Salesforce
uses: rdbumstead/setup-salesforce-action@v2
with:
auth_method: "sfdx-url"
sfdx_auth_url: ${{ secrets.SFDX_AUTH_URL }}To get your SFDX Auth URL:
sf org display --target-org YourOrg --verbose --json | jq -r '.result.sfdxAuthUrl'Store this as a GitHub secret named SFDX_AUTH_URL.
Run on Windows or macOS:
jobs:
test:
runs-on: windows-latest # or macos-latest
steps:
- uses: rdbumstead/setup-salesforce-action@v2
# Works on all platforms!Check:
- ✅ JWT key copied correctly (no line breaks)
- ✅ Client ID matches Connected App
- ✅ Username is correct
- ✅ Certificate uploaded to Connected App
Fix:
# Verify JWT key format
cat server.key | head -1
# Should show: -----BEGIN RSA PRIVATE KEY-----This is normal if:
- Your PR has no Salesforce metadata changes
- You only modified docs/tests
Check:
- Review the code analysis output
- Fix any violations
- Adjust
severity_thresholdif needed
See PLATFORM_SUPPORT.md and TROUBLESHOOTING.md for detailed help.
- 📖 Full Documentation
- 🔄 Migration Guide - Upgrade from v1 or v2
- 🖥️ Platform Support - Cross-platform details
- 🧪 Testing Strategy - Testing approach
- Cross-Platform Testing
- Test on Windows, macOS, Linux
- Matrix builds for comprehensive coverage
- See PLATFORM_SUPPORT.md
After setup, verify:
- PR creates and runs automatically
- Action installs CLI successfully
- Authentication works
- Validation command runs
Settings → Branches → Add rule:
- ✅ Require status checks to pass
- ✅ Require branches to be up to date
- ✅ Include administrators
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "none",
"overrides": [
{
"files": "*.{cls,trigger}",
"options": { "parser": "apex" }
}
]
}{
"extends": ["@salesforce/eslint-config-lwc/recommended"]
}{
"scripts": {
"test": "sfdx-lwc-jest"
},
"devDependencies": {
"@salesforce/sfdx-lwc-jest": "latest"
}
}- Ubuntu: Fastest, most cost-effective (recommended)
- Windows: If you need Windows-specific tooling (expect 2-3x slower execution)
- macOS: If your team uses Macs primarily
See PLATFORM_SUPPORT.md for details.
Setup time: ~15 minutes
Result: Enterprise-grade CI/CD pipeline ✅
Difficulty: Beginner-friendly 🟢
Platforms: Linux, Windows, macOS 🌐
Happy deploying! 🚀