Dylan AndersenDylan Andersen's Docs
Cursor + SalesforceAdvanced Cursor

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.

Multi-Org Workflows hero

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-uat for the ACME sandbox used for UAT.
  • acme-prod for the ACME production org.
  • acme-scratch for a scratch org used for ACME feature work.
  • demo-data-360 for a personal Data 360 demo org.
  • agentforce-storm for a @STORM-provisioned Agentforce org.
  • perf-sandbox for 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=false

That 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=false

Now 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.json pointing 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.

A single MCP pointed at whatever org is the default for the current project:

{
  "mcpServers": {
    "Salesforce DX": {
      "command": "npx",
      "args": [
        "-y", "@salesforce/mcp@latest",
        "--orgs", "DEFAULT_TARGET_ORG",
        "--toolsets", "orgs,metadata,data,users"
      ]
    }
  }
}

This is fine for personal work and scratch orgs. The MCP follows whichever project you're in.

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 RunLocalTests

Two reasons:

  1. If the default ever shifts, your command still does the right thing.
  2. 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 project

If the wrong org shows as default, fix it inline:

sf config set target-org=acme-uat --global=false

A short checklist before any destructive command

Before a deploy, delete, or data update on a customer org:

  1. sf config get target-org (or look at the .sf/config.json) and confirm the alias.
  2. Read the alias aloud. Really. Say "ACME UAT" before you hit Enter.
  3. For deploys, run --dry-run first.
  4. 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

On this page