Skip to content
October 21, 2025·12 min read

Git Workflow for Salesforce Projects: Branching, SFDX, and CI

Apply a practical Git branching strategy to Salesforce development — feature branches, scratch orgs, pull request validation, and automated deployment pipelines.

Back

Most Salesforce teams either have no Git workflow at all (change sets only) or they copied a software engineering branching model that doesn't account for org metadata dependencies. Here's a workflow tuned for Salesforce teams of any size.


Prerequisites

  • Salesforce CLI (sf) installed
  • Git installed and basic knowledge
  • GitHub or GitLab for pull requests
  • A Dev Hub org enabled for scratch orgs (optional but recommended)

Branch Strategy

main          ← production-ready, protected
  └── staging ← mirrors UAT/pre-prod org
        └── feature/TICKET-123-account-validation
        └── feature/TICKET-124-lwc-opportunities
        └── hotfix/TICKET-130-broken-trigger

Rules:

  • Direct commits to main are blocked
  • Every change goes through a PR targeting staging first
  • staging is merged to main only after UAT sign-off
  • Hotfixes branch from main, merge back to both main and staging

Project Structure

force-app/
  main/
    default/
      classes/         ← Apex classes + test classes
      triggers/        ← Apex triggers
      lwc/             ← Lightning Web Components
      objects/         ← Custom objects, fields, layouts
      flows/           ← Screen flows, autolaunched flows
      permissionsets/  ← Permission sets
      profiles/        ← Profiles (minimal, only what changed)
.forceignore           ← Like .gitignore for Salesforce metadata
sfdx-project.json      ← Project definition

.forceignore — always include these:

# Don't track managed package metadata
**/profiles/*.profile  # Profiles bloat fast — consider tracking only what changed
**/installedPackages/
**/objects/*/recordTypes/   # If org-specific
**/*.sharingRules
**/*.flowDefinition

Daily Developer Flow

# 1. Pull latest from staging
git checkout staging
git pull origin staging
 
# 2. Create feature branch
git checkout -b feature/TICKET-123-account-validation
 
# 3. Create scratch org for isolated development
sf org create scratch \
  --definition-file config/project-scratch-def.json \
  --alias ticket-123 \
  --duration-days 7
 
# 4. Push source to scratch org
sf project deploy start --target-org ticket-123
 
# 5. Develop in the scratch org...
# 6. Retrieve changes back to local
sf project retrieve start \
  --metadata ApexClass:AccountValidator \
  --target-org ticket-123
 
# 7. Commit and push
git add force-app/main/default/classes/AccountValidator*
git commit -m "feat(TICKET-123): add account validation on before insert"
git push origin feature/TICKET-123-account-validation
 
# 8. Open PR targeting staging

Pull Request Validation with GitHub Actions

# .github/workflows/validate-pr.yml
name: Validate PR
on:
  pull_request:
    branches: [staging]
 
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Install Salesforce CLI
        run: npm install -g @salesforce/cli
 
      - name: Authenticate Dev Hub
        run: |
          echo "${{ secrets.SF_DEVHUB_AUTH_URL }}" > devhub.txt
          sf org login sfdx-url --sfdx-url-file devhub.txt --alias devhub --set-default-dev-hub
 
      - name: Create Scratch Org
        run: |
          sf org create scratch \
            --definition-file config/project-scratch-def.json \
            --alias ci-org \
            --duration-days 1 \
            --target-dev-hub devhub
 
      - name: Deploy and Run Tests
        run: |
          sf project deploy start \
            --source-dir force-app \
            --target-org ci-org \
            --test-level RunLocalTests \
            --wait 30
 
      - name: Delete Scratch Org
        if: always()
        run: sf org delete scratch --target-org ci-org --no-prompt

Merging to Production

# 1. Merge staging → main after UAT sign-off
git checkout main
git merge staging --no-ff -m "chore: merge UAT release 2026.05.01"
 
# 2. Tag the release
git tag -a v2026.05.01 -m "Release 2026.05.01"
git push origin main --tags
 
# 3. GitHub Actions auto-deploys main to production
# .github/workflows/deploy-prod.yml
name: Deploy to Production
on:
  push:
    branches: [main]
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install CLI
        run: npm install -g @salesforce/cli
      - name: Authenticate Production
        run: |
          echo "${{ secrets.SF_PROD_AUTH_URL }}" > prod.txt
          sf org login sfdx-url --sfdx-url-file prod.txt --alias prod
      - name: Deploy
        run: |
          sf project deploy start \
            --source-dir force-app \
            --target-org prod \
            --test-level RunLocalTests \
            --wait 60

Common Pitfalls

  • Tracking profiles in full: profiles in SFDX format are extremely noisy — only track the delta or use permission sets instead
  • No scratch org definition: without project-scratch-def.json, developers share dev orgs and step on each other's changes
  • Large PRs: keep feature branches focused on one ticket — a PR that changes 30 objects is impossible to review
  • Merging without tests: the --test-level RunLocalTests flag on every deploy isn't optional in production

Resources

Aïcha Imène DAHOUMANE
About the author
Aïcha Imène DAHOUMANE
Salesforce Administrator & Developer · Consultant

Salesforce developer with a background in IT infrastructure — building reliable, maintainable solutions with Apex, Flows, LWC, and CI/CD.

ShareShare on LinkedIn