Multi-Org Workflows
Juggling 3 to 10 orgs the way real SEs actually do. Aliases, per-project defaults, workspaces, and multiple MCPs.
A typical week for an SE involves a scratch org for a personal build, a Data 360 demo org, two customer sandboxes, and an Agentforce-enabled @STORM org. Keeping them straight is its own skill. This page covers the patterns that actually work.

The scary default
sf config set target-org=... is machine-wide. If you set it once and forget, your next sf project deploy start might land in the wrong org. Every pattern on this page exists to prevent that.
An alias scheme that scales
Pick a naming pattern and stick to it. A shape that works:
<customer-or-purpose>-<type>[-<role>]Examples:
acme-uatfor the ACME sandbox used for UAT.acme-prodfor the ACME production org.acme-scratchfor a scratch org used for ACME feature work.demo-data-360for a personal Data 360 demo org.agentforce-stormfor a@STORM-provisioned Agentforce org.perf-sandboxfor a performance test sandbox.
If you can read the alias and tell me which org it is, the scheme is working. If you see dev1, test, orgA, and can't remember which is which, rename them:
sf alias set acme-uat=<current-bad-alias>
sf alias unset <current-bad-alias>Per-project default orgs
You almost never want a machine-wide default. Set the default at the project level:
cd ~/projects/acme-poc
sf config set target-org=acme-uat --global=falseThat writes to .sf/config.json inside the project. Any sf command run from that directory uses acme-uat without needing --target-org. Switch to ~/projects/widgetco-poc and the default changes with the working directory.
Commit .sf/config.json to the repo if the team should all use the same default, or add it to .gitignore if it's personal.
Dev Hub defaults, separately
Scratch orgs need a Dev Hub. Set that per-project too:
sf config set target-dev-hub=personal-devhub --global=falseNow sf org create scratch -f config/project-scratch-def.json uses the right Dev Hub without you thinking about it.
One Cursor workspace per customer
The single biggest win for multi-org sanity: open one Cursor window per customer. Each window has:
- Its own folder (for example,
~/projects/acme-poc). - Its own
.sf/config.jsonpointing at the right org. - Its own
.cursor/rules/scoped to that customer's conventions. - Its own chat history, which stays in that project's context.
- Its own MCP configuration (see below).
A chat that knows about ACME doesn't accidentally suggest a WidgetCo record type. You can't leak context between customers if the context never lived in the same window.
Multiple Salesforce MCPs
You can configure more than one Salesforce MCP in Cursor, each scoped to a different org. The trick is to give them distinct names so the agent can pick the right one.
For customer work, add a named MCP per org and lock the alias:
{
"mcpServers": {
"Salesforce ACME UAT": {
"command": "npx",
"args": [
"-y", "@salesforce/mcp@latest",
"--orgs", "acme-uat",
"--toolsets", "orgs,metadata,data"
]
},
"Salesforce ACME Prod (read-only)": {
"command": "npx",
"args": [
"-y", "@salesforce/mcp@latest",
"--orgs", "acme-prod",
"--toolsets", "orgs,data"
]
}
}
}Then in chat, name the MCP explicitly:
Using the `Salesforce ACME UAT` MCP, deploy force-app and run all
local tests.The production MCP has a reduced toolset (no metadata, no users), so the agent cannot accidentally deploy there.
Why the separate 'prod (read-only)' server?
You still want to query production sometimes. Giving it its own scoped MCP with a clearly different name makes misfires obvious. Asking "the Salesforce MCP" about a record becomes "which Salesforce MCP?", which is the question you want the agent to answer before doing anything destructive.
Running commands against a specific org, always
When you're typing CLI commands directly, get in the habit of always passing --target-org:
sf project deploy start --target-org acme-uat
sf data query --query "SELECT Id FROM Account LIMIT 5" --target-org demo
sf apex run test --target-org acme-uat --test-level RunLocalTestsTwo reasons:
- If the default ever shifts, your command still does the right thing.
- When you paste a command into chat or documentation later, it carries the target with it and is runnable without context.
A helper shell function
Some SEs add a tiny shell helper to confirm the target org before any risky command:
# ~/.zshrc or ~/.bashrc
sfdeploy() {
local target
target=$(sf config get target-org --json | grep -o '"value":"[^"]*' | sed 's/"value":"//')
echo "About to deploy to: ${target}"
read -r -p "Continue? (y/N) " reply
[[ "$reply" =~ ^[Yy]$ ]] && sf project deploy start "$@"
}Then use sfdeploy instead of sf project deploy start on days where you're switching contexts a lot.
Switching fast
Two commands you'll use constantly:
sf org list # show all authenticated orgs, with aliases and usernames
sf config list # show the current default target-org for this projectIf the wrong org shows as default, fix it inline:
sf config set target-org=acme-uat --global=falseA short checklist before any destructive command
Before a deploy, delete, or data update on a customer org:
sf config get target-org(or look at the.sf/config.json) and confirm the alias.- Read the alias aloud. Really. Say "ACME UAT" before you hit Enter.
- For deploys, run
--dry-runfirst. - For data, run the SOQL version before the DML version.
Three seconds of verification per command beats one very bad afternoon.
What to do next
- Lock safety rules into your repo: Rules & AGENTS.md.
- Scope auto-run so the agent can't run the risky commands without asking: Security & Data Handling.
- Add more MCPs beyond Salesforce: MCP Beyond Salesforce.