Orgbench — compare two Salesforce orgs and deploy only the difference,
without leaving VS Code.
Pick a source and a target — a Salesforce org or a local git repository on
either side — choose the metadata you care about, and Orgbench shows what's
added and what changed, component by component, with a side-by-side diff on any
row. Tick what you want moved. Everything runs on your machine through the
Salesforce CLI you're already authenticated with: no cloud service, no vendor
account, no managed package in your org, and your metadata never leaves your
laptop.

A native core, not a web app. Orgbench is a single native binary (written
in Rust) driving your local Salesforce CLI — no bundled Node runtime, nothing
installed in your org. It runs outside the editor's process, so a large
comparison — hundreds of components across dozens of metadata types — neither
bloats VS Code's memory nor takes it down. It is the same core that powers the
Orgbench plugin for IntelliJ IDEA, so the comparison and deployment rules are
identical in both.
What you can do with it
Find and pick exactly what to deploy. Filter by label or API name across
objects, fields, Apex, LWC/Aura, Visualforce, layouts, flows — up to 172
metadata types in 14 groups; selections across types accumulate into one
session you can export to a file and import back later.

Move the field and its permissions in one go. Field-level security, object
CRUD, Apex class and Visualforce page access, tab visibility and record type
visibility travel with the components you checked — additively (never revokes)
or as a full sync — into exactly the Permission Sets and Profiles you select.
Managed-package and standard components get rows too, as permission subjects —
their permissions travel while their metadata never deploys. The part change
sets and the CLI leave to you.

Choose how much risk you want. Validate only dry-runs the whole package
against the target. All or nothing sends it as one deployment that rolls back
completely on any failure. Best-effort sends the same package but keeps every
component Salesforce accepted and reports the ones it rejected — so a big
package lands in one pass, and your next run only deals with what failed.
Quick Deploy promotes an already-validated package without uploading or
re-running tests.

Fix it in the diff, ship the fix. The source side of the diff view is
editable for any component whose row is a single document — Apex, Visualforce
pages, objects, fields, flows, layouts and more — and the deployment sends your
edited version. The source org is never modified, the edit is marked on the row
and can be reverted, and it lives only inside that comparison session. Bundles
and multi-component container files stay read-only.
Deploy one field on a page layout. Switch page layouts to "Select fields
and related lists" and move a single field or related list into the higher org
without touching anything else on that layout.
Use git as either end. Org → org, org → git, git → org, git → git. A git
target ends in one local commit containing only what you selected — Orgbench
never pushes and never switches your branches.
Let dependencies pick themselves. Checking a field pulls in its missing
object; checking a list view pulls in the fields it references — visibly, as
rows you can uncheck.
What it never does
- Never deletes anything in the target — a component that exists only in
the target produces no row and is never touched.
- Never deploys managed-package or standard-object metadata — such rows
exist to carry permissions, and a managed or standard field deploys at most
its two history-tracking toggles, spliced into the target's own document,
never the source definition.
- Never creates an object you didn't tick: auto-selected dependencies are
visible rows you can uncheck before deploying.
- Never touches your tokens — authentication stays in the Salesforce CLI,
and diagnostics are sanitized before you see them.
- Partial deployment happens only in the mode built for it: Best-effort
keeps what succeeds because you chose that trade-off — and it is not offered
against production orgs — while All or nothing rolls back the whole package
on any failure. A package carrying Apex or Flows always passes through the
test-selection screen before it goes to any org.
Requirements
- macOS arm64, macOS x64 or Windows x64 (the bundled core is fail-closed on
other platforms; Linux is planned). The Windows x64 build is for Windows on
x64; it has not been verified on Windows on ARM, and VS Code itself may
reject installing an x64-targeted VSIX (
targetPlatform: win32-x64) there.
- VS Code 1.85 or newer.
- Salesforce CLI (
sf) installed and on PATH. On Windows the core also looks
for sf.exe, sf.cmd, sf.bat and sf in %LOCALAPPDATA%\sf\client\bin.
- Two authenticated aliases that resolve to different Salesforce org IDs. The
terminal is not the only way to create them: the + button next to the
Source or Target dropdown runs the CLI's own
sf org login web for
you (see the workflow below).
Install from disk
- Obtain the VSIX for your platform:
orgbench-darwin-arm64.vsix (Apple
silicon), orgbench-darwin-x64.vsix (Intel) or orgbench-win32-x64.vsix
(Windows x64), or build it (below).
- In VS Code, open the Extensions view, choose Install from VSIX… from the
… menu, and select the file.
- Open the Orgbench view from the Activity Bar.
Workflow
- Choose the Source and the Target. Two orgs must resolve to different
org IDs; either side can be a local git repository instead — see below.
The + button next to either dropdown creates a new alias without leaving
the editor: enter the alias name, choose Production, Sandbox or a My Domain
URL, and sign in in the browser the Salesforce CLI opens. The new alias is
selected on the side you asked from. The extension never sees your
credentials, never sets a default org and never overwrites an existing alias
— a name already in use is refused before the browser opens. While the login
runs, the selection screen stays where it is with its controls disabled and
offers Cancel; cancelling leaves your selection untouched.
- Pick a Metadata Type and tick the components you want. Selections
accumulate across types into one session manifest: switching the type does
not discard what you ticked before. Export Session… writes the session's
current selections to a scenario file, Import Session… reads one back —
see Session export and import below. Reload Orgs re-reads the alias
list.
- Choose Compare.
- Review the differences and tick the rows to deploy. Double-click a row to
open both versions of that component side by side. An Apex class, an Apex
trigger and a Visualforce page are two documents each — the body and its
*-meta.xml sidecar — so such a row opens the sidecar in its own read-only
window when the row has attribute differences (an API version, a status or a
label change), and the body window whenever the sidecar window does not open
or the body differs too. A row whose only difference is a sidecar attribute
therefore opens the sidecar alone: there is no body window for it, because
the two bodies are identical. The left pane of the diff is editable while
orgbench.allowSourceEditing is on and the core marked that row editable —
a metadata XML document, or the body of an Apex class, an Apex trigger or a
Visualforce page; a sidecar, a bundle, a rule or label sharing one file, a
single layout element and any row with nothing to deploy stay read-only. So
the deployment can send your own increment instead of the whole
current document of the source org: type in it, and about a second and a half
after you stop the draft is sent to the core and the row picks up its
Edited mark. The draft is saved as you type, so the tab never shows
unsaved changes and never asks whether to keep them; closing the tab sends
whatever is left, and pressing a deploy button sends everything still unsent
before the deployment starts. Orgbench: Revert
Edit in the command palette drops the edit and puts the original text back;
it acts on the pane your caret was last in, so click into the left pane
first. Retyping the original text usually drops the edit too, but only the
command is guaranteed to: a body whose line endings are mixed, or one that
starts with a byte-order mark, is not loaded byte for byte. The draft is held
in memory and never written to disk, and it goes away with the comparison.
Revert only works from the comparison screen itself; run it from the test
selection or permission screen and it answers "Revert is available on the
comparison screen." instead of reverting anything.
Pick a permission transfer mode in Settings — search for Orgbench › Fls
Mode (the setting is orgbench.flsMode; its label is unchanged from
before wave A, but its three values now read Do not transfer
permissions, Additive permissions and All permissions) — then
start the deployment: against an org the row of final buttons is Validate
(--dry-run, nothing is written), Deploy (all or nothing — one atomic
package, any failure rolls everything back) and Best Effort (the same
single package sent with --ignore-errors, so Salesforce keeps every
component it accepted and only the ones it named are Failed); Best
Effort is not offered at all against a target the core has recognized as
production. Every mode sends the whole selection as one Metadata API
deployment — there are no waves or batches, and no ordering between
metadata types. Against a git repository there is a single Deploy.
- If the package contains Apex classes, Apex triggers or flows and the target
is an org, an Apex test screen comes first; an empty selection means no
--test-level is passed, and a production org still runs its own local
tests and enforces 75% coverage.
- With a transfer mode other than Do not transfer permissions, the
containers that will receive the permissions are listed for confirmation
before the deployment runs. The list distinguishes Existing containers,
matched by kind and API name in both orgs, from New Permission Sets
that exist only in the source and are eligible for creation, and can be
filtered by the Presence column. Each row carries two names: the Setup
Label and the metadata full name in the Container column. They
differ for many standard profiles —
Standard User is Standard,
System Administrator is Admin — and only the full name is used to
match, retrieve, package and merge. The label shown is the target org's
for a matched container and the source org's for a newly creatable
Permission Set, so it always names the org where you would go check the
result in Setup. The permission result table shows the
same pair. Which permissions travel is decided by
the rows you checked: a custom field row carries its field-level security,
a custom object or platform event row carries the seven object
permissions — create, read, edit, delete, View All Records, View All
Fields and Modify All Records — an Apex class row and a Visualforce page
row carry access to that component, a custom tab row carries that tab's
visibility, and a record type row carries whether that record type is
visible. A managed-package or standard component gets a row too, so its
permissions can be selected even though its own metadata never deploys: the
Details column reads Permissions only for a managed Apex class,
Visualforce page, tab or record type, a managed or standard object, or a
standard tab — the row has no document, so double-clicking it opens no
window — and Tracking only for a managed or standard field, where the
only metadata that travels is the Track History and Track Feed
History toggles, merged into a fresh copy of the target's own field
document. Two boundaries: the default record type of the target is never
changed by a transfer, and tabs of both kinds are transferred — a custom
tab travels as its own metadata component, while a standard tab such as
standard-Account travels as a Permissions only row whose name is the
permission entry itself. Both kinds of row are visible
only when the UI & Navigation and Objects & Fields metadata
categories are enabled. A selected source-only Permission Set may be
created in the target, populated with only the selected rows' permissions
and never a license, a user permission, an application visibility or a
Profile.
- Read the result tables. Copy details copies only the sanitized
diagnostics — never a token. Quick Deploy appears after a successful
Validate run that actually ran Apex tests, and deploys the very package
Salesforce already validated without re-running them; a validation that ran
no tests says so instead, because Salesforce refuses to quick-deploy it.
New comparison starts over.
Comparison or deployment can be cancelled from the progress view, and so can a
browser login.
Session export and import
Export Session… writes what the current session has selected so far to a
JSON scenario file (default name session.json). The button is on five views —
object selection, differences, Apex tests, permission containers and results —
and each writes what is known by then. The whole file is still serialized and
written by the extension's core, but the core does not already hold what goes
into it: the webview sends the live marks of the screen the button was
pressed on, and the host only picks the path.
The file holds no snapshot of the comparison — no difference rows, no statuses,
no attribute values — because such a snapshot goes stale the moment either end
changes. It holds no secrets either: metadata names, org aliases, branch names
and modes only, and a git end is written as the repository directory's last
segment plus the branch, never as an absolute path.
An absent field and an empty one mean different things: no apexTests key means
the test view was never reached, "apexTests": [] means it was reached and no
test was checked. deployMode travels with deployModeIsFinal, which is true
only on the results view. The file carries its own version, independent of the
protocol version.
Import Session… is a button on the object selection view that reads a
scenario file back in — a version-1 or version-2 file, either one written by
Export Session or handed to you by someone else. It restores both endpoints
and the session manifest, the checked comparison rows and the permission
transfer mode, the checked Apex test classes, and the checked permission
containers, each applied once the matching view is built. Every mark is
applied exactly once per set, so a checkbox you clear afterward does not come
back just because the view is rebuilt for another reason. Import does not
navigate anywhere: you still walk the wizard yourself, and each view shows its
marks already checked when you reach it. A name the file carries but the
extension no longer accepts — renamed, deleted, or changed type since the
file was written — does not fail the import; it is listed instead, on the
view where it turned out to be unaccepted. A git endpoint is never restored,
because the file, like every other output of the extension, carries only the
repository directory's last path segment and the branch name, never the
absolute path — you choose a git endpoint by hand after importing. The deploy
mode recorded in the file is only shown as a text hint next to the deploy
summary; it is never pressed on your behalf.
Git source and target
Each side is chosen independently, so org-to-org, org-to-git, git-to-org and
git-to-git all work. A repository side is an absolute path plus a branch:
Browse… picks the folder, and the extension reads the SFDX layout declared
by its sfdx-project.json. A deployment into a repository ends in exactly one
local commit and never pushes; the extension never checks out a branch in your
repository — another branch is written through a temporary worktree that is
removed afterwards. The + button next to the branch dropdown names a new
branch, which is created by that commit alone.
Filtering
Every webview table (components, differences, Apex tests, permission
containers, and both result tables) shares one per-column filter row directly
under its header:
- Text columns get a free-text box that matches a substring anywhere in the
value, case-insensitively; a leading and/or trailing
* is optional and
ignored.
- Enum columns — Change, Kind, Presence, Operation, Status — get a
multi-select popup listing every value with checkboxes. Two carry a smart
default: the differences Change popup hides
No change rows, and the
permission-results Status popup hides Skipped.
- The selection column gets a tri-state toggle.
Column filters combine with AND. Filtering only hides rows: a row that is
checked and then hidden by a filter keeps its selection, and the selection
column's header checkbox selects or clears only the currently visible,
filtered rows (hidden selections are left untouched). Filters reset to their
defaults when you leave the screen (a new comparison, or stepping between the
review and permission-container screens).
Selections (chosen components, deployable rows, and permission containers), together
with each table's column filters and sort, are preserved when the view is
hidden and shown again, so switching away from the panel never re-checks a
container you deselected before deploying or drops the filters you set. Filters
and sort persist while you stay on a screen and reset only when you leave it.
Sorting
Click any column header except the selection column to sort the table by that
column. Each click cycles the sort: ascending (▲), then descending (▼), then
off (original order). One column sorts at a time. Enum columns sort by their
displayed label; boolean columns (Read/Edit) sort unchecked-before-checked.
Sorting only reorders rows — it never changes which rows a filter has hidden —
and, like the column filters, persists while you stay on a screen, resetting to
the original order when you leave it.
Settings
orgbench.layoutMode — how Page Layouts are compared. whole is the
default: one row per layout, compared over the whole document, and a
deployment replaces the target layout with the source version, so an element
that exists only in the target is gone afterwards. elements compares a
layout element by element — one row is a field or a related list — and merges
only the selected elements into the target layout. The mode applies to the
next Compare; rows already on screen keep the form they were built with.
orgbench.allowSourceEditing — allow editing metadata in the diff view. On
by default: the left pane is then editable for a row whose document belongs
entirely to it — a metadata XML document, or the body of an Apex class, an
Apex trigger or a Visualforce page — and the deployment sends the edited
document. The *-meta.xml sidecar of those three, a Lightning or Aura
bundle, a rule or label that shares a file with its siblings, a single layout
element and the target pane stay read-only either way. The key keeps its
historical name so a cleared box survives the upgrade. Clearing it does not
erase edits that already exist — it gates the FIRST edit of a row, not access
to one that already exists, so a row already carrying an edit reopens
editable. The value is read when a window is opened, so a window already on
screen keeps the form it was opened with.
orgbench.flsMode — permission transfer mode used by every deployment:
NONE (do not transfer permissions), ADDITIVE or ALL. The key keeps its
historical name so existing configurations keep working.
orgbench.metadataCategories — which categories of metadata types the
Metadata Type dropdown offers. Core alone is the default.
orgbench.logLevel — session log detail: off, standard or detailed.
No token ever reaches the file, and no response body either, except the
sanitized diagnostic of a failed call — the same text the UI shows.
The live progress line (phase, step counter, elapsed time) is always shown while
an operation runs; there is no setting for it.
Licensing
Orgbench is free during early access. A paid tier for advanced features is
planned; nothing is gated today, and every feature described above is available
to everyone.
The license path is fully offline: validation is a local signature check inside
the bundled sf-core-server — there is no license server, no phone-home and no
network call of any kind, not even a revocation check. Any licensing failure
degrades to the free tier and keeps working, and safety is never gated: every
boundary the repository's root README.md lists applies in every tier.
Enter or remove a key with the Orgbench: Manage License command — the key
icon in the view title opens it — and the picker's own title shows what the core
decided, including the reason a key was not accepted. The key and the first-run
timestamp live in a single file the core owns, inside your user data directory
(~/Library/Application Support/orgbench/ on macOS, %APPDATA%\orgbench\ on
Windows), so a key entered in either editor applies in both once that editor's
core has restarted and read the file.
There is no telemetry: nothing about you or your orgs is collected or
transmitted. The extension is distributed under the
End User License Agreement; see also the
Privacy Policy and the
third-party notices.
Development
The environment variable ORGBENCH_CORE_SERVER_PATH overrides the sf-core-server
binary shipped inside the VSIX: set it to the absolute path of your own build in the
environment VS Code is started from. It is a development tool and has no setting in
the Settings UI; leave it unset to use the bundled binary. A value pointing at
something that is not a regular file fails the launch and the refusal names the
variable.
Build
The extension is part of the repository's Gradle build:
cd vscode && npm ci
cd vscode && npm test # tsc typecheck + node:test suite
cd vscode && npm run build # esbuild: extension-host + webview bundles
./gradlew buildVsixAarch64 # writes vscode/orgbench-darwin-arm64.vsix
./gradlew buildVsixX64 # writes vscode/orgbench-darwin-x64.vsix
./gradlew buildVsixWin32X64 # writes vscode/orgbench-win32-x64.vsix
The three tasks share vscode/bin/, so they run one after another, never in
parallel. Building the Windows VSIX from macOS needs cargo install cargo-xwin
and rustup target add --toolchain 1.97.0 x86_64-pc-windows-msvc.
Each VSIX bundles exactly one native core binary for its target.
Troubleshooting
The support site https://orgbench.io/ answers questions about the extension and files a ticket; the question-mark icon in the view title opens it, and the link opens only when you choose it.
The Salesforce core process did not start
The extension launches a bundled sf-core-server binary from its bin/
directory. If the view reports that the core process could not start or
terminated, confirm the platform is supported (macOS arm64, macOS x64 or
Windows x64) and that the VSIX was installed without modification. On Windows
the bundled binary is unsigned, so SmartScreen or the antivirus may quarantine
it on first launch. The core process log is written to the Orgbench Core
output channel.
No aliases are listed
Use the + button next to the Source or Target dropdown to log in to an
org and create an alias without leaving the editor, or run sf org list --json in
a terminal, authenticate aliases there and use the Reload Orgs button. Entries
without an alias are intentionally not shown.
While a browser login is running, the selection screen stays where it is with its
controls disabled, shows the live progress line and offers Cancel; cancelling
stops the CLI and leaves the browser tab open for you to close. A login that is not
finished within ten minutes is reported as such. In a headless or container
environment the Salesforce CLI refuses a browser login outright, and the extension
reports that instead — authenticate in a terminal there.
Source and target are rejected as the same org
Choose aliases that resolve to different org IDs; two alias names can point to
one org.
Salesforce is a trademark of Salesforce, Inc. Orgbench is an independent
product and is not affiliated with, endorsed by, or sponsored by Salesforce.