Development Workflow
This guide walks through the development workflow in a project that uses sfp: fetching an environment, synchronising source with it, testing, and submitting the change.
Prerequisites
An sfp server connection
sfp runs against an sfp server. Commands that reach the server — the sfp pool commands and sfp package create — take a --repository flag identifying the repository they act for, in the form owner/repo for GitHub and GitLab, or org/project/repo for Azure DevOps. The flag defaults to the GITHUB_REPOSITORY or GITLAB_REPOSITORY environment variable, so it can be omitted in a pipeline but must be supplied when you run these commands locally.
The server URL comes from your sfp configuration or from SFP_SERVER_URL.
DevHub access
A DevHub is required for building packages, creating scratch orgs, and creating sandboxes. sfp build takes the DevHub as a required -v flag, so a build fails without one.
Add your user to the DevHub org following Salesforce's guide, then authenticate:
sf org login web --alias mydevhub --set-default-dev-hub
sf org display --target-dev-hub1. Get a development environment
Environments are fetched from server-managed pools. The same commands serve scratch org and sandbox pools; which one a tag maps to is set in the pool configuration on the server.
# List what is available in a pool
sfp pool list --repository myorg/myrepo --tag dev-pool
# Fetch an org and give it a local alias
sfp pool fetch --repository myorg/myrepo --tag dev-pool --alias my-feature-org
# Fetch and set it as the default org
sfp pool fetch --repository myorg/myrepo --tag dev-pool --alias my-feature-org --set-default
# Extend the assignment before it expires
sfp pool extend --repository myorg/myrepo --tag dev-pool --assignment-id <assignment-id> --extension-hours 24
# Return the org to the pool
sfp pool unassign --repository myorg/myrepo --tag dev-pool--repository and --tag are required on all four commands; sfp pool extend also requires --assignment-id.
Scratch orgs are typically fetched per story and sandboxes at the start of an iteration, but that is a team convention rather than a constraint of the commands.
Creating a sandbox directly
Where a pool is not in use, a sandbox can be created against the DevHub:
sfp sandbox create -v mydevhub -n my-feature-sandbox -s SourceSandbox-n names the sandbox and -s names the sandbox to copy from. Passing a definition file with -f is mutually exclusive with -n.
Working with the org
# Open the org
sfp org open --targetusername my-feature-org
sfp org open --targetusername my-feature-org --browser chrome
# Set a default org for subsequent commands
sfp config set target-org my-feature-org
sfp config set target-org my-feature-org --global2. Develop
Pull the latest metadata
# Pull using source tracking
sfp pull --targetusername my-feature-org
# Pull a specific package
sfp pull --targetusername my-feature-org --package my-package
# Pull and ignore conflicts
sfp pull --targetusername my-feature-org --ignore-conflictsWithout --package, --domain or --source-path, pull uses source tracking and retrieves only what changed in the org. With any of those three, it retrieves everything within that scope. Pull also converts environment-specific values back to placeholders where the package configures replacements. See Pull Changes from your org.
Add packages
A package is a directory plus an entry in sfdx-project.json. sfp package create adds both:
sfp package create source -n feature-payment -r src/payment-processing --repository myorg/myrepo
sfp package create data -n reference-data -r data/reference-data --repository myorg/myrepoSee Creating a package for the flags each package type accepts.
Push changes to the org
# Push using source tracking
sfp push --targetusername my-feature-org
# Push a specific package
sfp push --targetusername my-feature-org --package my-package
# Push everything, ignoring conflicts
sfp push --targetusername my-feature-org --ignore-conflictsPush applies the package's configured replacements before deploying. --ignore-conflicts cannot be combined with --package, --domain or --source-path. See Push Changes to your org.
3. Run tests
sfp executes Apex tests per package rather than across the whole org:
# Test one package
sfp apextests trigger -o my-feature-org -l RunAllTestsInPackage -n sales-core
# Test several packages
sfp apextests trigger -o my-feature-org -l RunAllTestsInPackage -n sales-core -n sales-ui
# Test every package in a domain
sfp apextests trigger -o my-feature-org -l RunAllTestsInDomain -r config/release-config-sales.yaml
# Run named tests during development
sfp apextests trigger -o my-feature-org -l RunSpecifiedTests --specifiedtests PaymentProcessorTest
# Validate per-class coverage
sfp apextests trigger -o my-feature-org -l RunAllTestsInPackage -n sales-core -c -p 80The coverage flags -c and --validatepackagecoverage are accepted only with RunAllTestsInPackage. See Running Apex Tests for test levels, coverage rules, and output formats.
4. Build locally
Building locally confirms the packages resolve and produce artifacts before the pipeline does it:
# Build every package
sfp build -v mydevhub
# Build a domain
sfp build -v mydevhub --domain sales
# Build named packages only
sfp build -v mydevhub -p payment-processing
# Build against a branch, with a build number, skipping unchanged packages
sfp build -v mydevhub --branch feature/payment --buildnumber 123 --diffcheck-v is required. --domain is an alias of --releaseconfig and takes the release config name. -p (--buildOnly) restricts the build to named packages. Artifacts are written to artifacts unless --artifactdir says otherwise.
5. Inspect dependencies
# Show a package's direct and transitive dependencies
sfp org dependency explain --package payment-processing
# Make every transitive dependency explicit in sfdx-project.json
sfp org dependency expand -v mydevhub
# Remove redundant transitive dependencies
sfp org dependency shrink -v mydevhubexpand and shrink require a DevHub; explain does not. See Dependency Management.
6. Submit the change
Commit and raise a pull request as normal. The pipeline then validates the change:
sfp validate org --repository myorg/myrepo --targetorg validation-org --domain sales --mode thorough --coveragepercent 75--repository, --sfp-server-url, --targetorg and --domain (an alias of --releaseconfig) are required; the first two default from environment variables in a pipeline. --mode accepts individual or thorough and defaults to thorough. See Validating a change.
7. After merge
The pipeline builds the merged change and publishes the artifacts:
sfp build -v mydevhub --branch main
sfp publish -d artifacts --npm --scope myorg --npmrcpath .npmrcsfp publish has no registry-URL flag — the registry is read from the .npmrc passed with --npmrcpath.
Common tasks
Environment-specific values
Values that differ per environment are handled with replacements configured in the package's preDeploy/replacements.yml, applied on push and reversed on pull. See String Replacements.
Structural differences — whole files that differ per environment — are handled by aliasified packages instead. When you pull or push a package that sets aliasfy, sfp resolves the directory matching the target org's alias. See Aliasfy packages.
Deleting metadata
Deletions are declared as destructive changes in the package rather than by removing files alone. See Destructive Changes.
Troubleshooting
Push or pull reports conflicts
sfp pull --targetusername my-feature-org --ignore-conflicts
sfp push --targetusername my-feature-org --ignore-conflictsOn push, --ignore-conflicts applies to the whole org and cannot be scoped to a package.
An empty pool
sfp pool list shows what a pool holds. Replenishment is a server operation:
sfp server pool replenish --repository myorg/myrepo --tag dev-poolPool size and replenishment are set in the pool configuration on the server (sfp server pool config update). If a pool is consistently empty, raise it with whoever administers the server rather than replenishing by hand.
Build failures
sfp build -v mydevhub -p problematic-package --loglevel DEBUG
sfp org dependency explain --package problematic-packageDevHub connection
sf org login web --alias mydevhub --set-default-dev-hub
sf org display --target-dev-hub
sf limits api display --target-org mydevhub