---
url: /salesforce-devops-starter-kit/workflows/scratch-org-ci.md
description: Reusable workflow that runs a full scratch org lifecycle.
---

# Scratch Org CI

Reusable workflow that runs a full scratch org lifecycle: it authenticates to the Dev Hub, creates a scratch org, optionally installs dependency packages, deploys the source, optionally assigns permissions and runs anonymous Apex setup scripts, runs Apex tests, and always deletes the scratch org again.

> Reusable workflow - call it from your own repository with `uses:`. [View source](https://github.com/svierk/salesforce-devops-starter-kit/blob/main/.github/workflows/scratch-org-ci.yml)

## Inputs

| Input | Description | Type | Required | Default |
| --- | --- | --- | --- | --- |
| `source-dir` | Source directory to deploy to the scratch org. | string | no | `force-app` |
| `definition-file` | Path to the scratch org definition file. | string | no | `config/project-scratch-def.json` |
| `duration-days` | Number of days before the scratch org expires. | string | no | `1` |
| `test-level` | Apex test level for the test run. Ignored when apex-tests is set. | string | no | `RunLocalTests` |
| `apex-tests` | Comma-separated or multi-line list of Apex tests to run as "Class" or "Class.method". A selection always runs with RunSpecifiedTests. Leave empty to run the test-level. | string | no | - |
| `packages` | Comma-separated or multi-line list of dependency packages (aliases or IDs) to install, e.g. "04t...,04t...". Leave empty to skip. | string | no | - |
| `permission-sets` | Comma-separated or multi-line list of permission sets to assign to the scratch org admin user before the tests run. Leave empty to skip. | string | no | - |
| `permission-set-groups` | Comma-separated or multi-line list of permission set groups to assign to the scratch org admin user. Leave empty to skip. | string | no | - |
| `permission-set-licenses` | Comma-separated or multi-line list of permission set licenses to assign to the scratch org admin user. Leave empty to skip. | string | no | - |
| `apex-file-path` | Comma-separated anonymous Apex file(s) or glob(s) to execute before the tests, e.g. "./scripts/apex/setup/\*.apex". Leave empty to skip. | string | no | - |

## Secrets

| Secret | Description | Required |
| --- | --- | --- |
| `SFDX_CONSUMER_KEY` | Consumer key (client ID) of the Dev Hub connected app used for the JWT flow. | yes |
| `SFDX_JWT_SECRET_KEY` | Contents of the private server key (server.key) used for the JWT flow. | yes |
| `SFDX_USERNAME` | Username of the Dev Hub integration user to authenticate as. | yes |

## Example usage

Copy this caller into your project's `.github/workflows/` directory ([source](https://github.com/svierk/salesforce-devops-starter-kit/blob/main/examples/scratch-org-ci.yml)):

```yaml
# Example: call the reusable scratch org CI workflow.
# Copy this file into your project's .github/workflows/ directory.

name: Scratch Org CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Least-privilege token: the called workflow only reads the repository and talks
# to Salesforce. A reusable workflow can never get more permissions than the
# caller grants here.
permissions:
  contents: read

jobs:
  scratch-org-ci:
    uses: svierk/salesforce-devops-starter-kit/.github/workflows/scratch-org-ci.yml@v1.5.0
    with:
      source-dir: force-app
      definition-file: config/project-scratch-def.json
      duration-days: '1'
      test-level: RunLocalTests
      # Run only a selection of Apex tests instead (implies RunSpecifiedTests):
      # apex-tests: AccountServiceTest,LeadServiceTest.testConversion
      # Optional dependency packages to install before deploying:
      # packages: '04t...,04t...'
      # Optional permissions and setup scripts applied before the tests run:
      # permission-sets: 'My_App_Admin'
      # apex-file-path: ./scripts/apex/setup/*.apex
    secrets:
      SFDX_CONSUMER_KEY: ${{ secrets.SFDX_CONSUMER_KEY }}
      SFDX_JWT_SECRET_KEY: ${{ secrets.SFDX_JWT_SECRET_KEY }}
      SFDX_USERNAME: ${{ secrets.SFDX_USERNAME }}
```
