nimbus app runs a Salesforce Multi-Framework UI bundle — a Vite-built React app — against the local Nimbus runtime. The API calls it makes land on a Salesforce Apex interpreter and an embedded PostgreSQL database on your machine. No scratch org, no sandbox, no internet.
nimbus appRequires a Pro license. The command also answers to its legacy alias, nimbus ui.
Salesforce ships sf ui-bundle dev: a local proxy that spawns Vite, injects SFDC_ENV, and forwards the app’s API calls to a real org. The UI half of that loop is already fast. The other half is not — every request leaves your machine, and every change to the Apex or schema behind the screen is a deploy and a wait.
nimbus app keeps the proxy design, the default port, and the SFDC_ENV contract, and swaps the destination. Requests terminate in-process against your project’s own Apex classes, triggers, flows, and object metadata. Latency is loopback. The whole loop works on a plane.
What a bundle means here, precisely. This is Multi-Framework UI bundle hosting: directories under force-app/**/uiBundles/<name>/ carrying a *.uibundle-meta.xml marker and a package.json. It is not a local harness for standalone Lightning Web Components or Aura components, and @AuraEnabled wire adapters are not part of the served surface — the SDK path a Multi-Framework app uses is REST, GraphQL, and Apex REST, and that is what Nimbus answers.
# Before: the bundle talks to an org
sf ui-bundle dev
# -> vite on :5173
# -> proxy on :4545
# -> every API call crosses the network
# -> Apex change = deploy, wait, retry
# After: the bundle talks to your machine
nimbus app
# -> vite on :5173 (unchanged)
# -> proxy on :4545 (same default)
# -> every API call hits the local interpreter
# -> no org, no network, no deploywindow.SFDC_ENV shape the Salesforce SDK already expects, so the app cannot tell the difference.Four steps, all of them on your machine. Nothing is stubbed: the runtime behind the proxy is the same one nimbus test runs your Apex tests on.
Nimbus walks the project for UI bundles under force-app/**/uiBundles/<name>/, keyed off the *.uibundle-meta.xml marker, and reads each bundle’s ui-bundle.json. One bundle in the project and it is used automatically. Several, and you either name one, get an interactive picker in a terminal, or a listed error in CI. Run nimbus app list to see exactly what Nimbus can find.
Nimbus does not reimplement Vite. It spawns the bundle’s configured dev command — npm run dev by default — so HMR, source maps, and your existing vite.config.ts behave exactly as they already do. Pass --no-spawn to attach to a Vite process you started yourself.
A proxy on 127.0.0.1:4545 — the same default address sf ui-bundle dev uses — rewrites the served index.html to define window.SFDC_ENV: basePath, instanceUrl, apiVersion, accessToken, userId. That is the object the Salesforce SDK reads at boot, so the app authenticates and routes calls without a single code change.
Every /services/data/* and /services/apexrest/* request from the app lands on the embedded runtime: the Apex interpreter plus embedded PostgreSQL, in-process. Your triggers, flows, validation rules, and @RestResource classes run for real against local data. Anything outside the served surface returns 501 with the path, so gaps are visible rather than silently mocked.
Bundle selection is deliberate about CI. A single bundle resolves with no argument. Several bundles with no argument opens a numbered picker when stdin is a terminal, and fails with the bundle list when it is not — so a pipeline never hangs on a prompt.
--all serves every discovered bundle at once under /lwr/application/<name>/, the production-faithful path, with an index at root. They share one runtime and therefore one dataset — which is what a real org looks like.
Four subcommands wrap the bundle’s own npm scripts so CI needs no bundle-aware logic of its own: build, test, preview, and list. A fifth, schema, replaces npm run graphql:schema — it derives the GraphQL SDL from your local SObject metadata, byte-identical to what the running server advertises.
# Single bundle: nothing to choose
nimbus app
# Name one explicitly
nimbus app reactRecipes
# Every bundle at once, pick what opens
nimbus app --all --primary reactRecipes
# Attach to a vite you started yourself
nimbus app reactRecipes --no-spawn
# Custom proxy address; skip the browser
nimbus app --addr 127.0.0.1:4546 --no-open
# Install deps if node_modules/ is missing
nimbus app --install
# See what nimbus can find
nimbus app list
# Codegen without an org to introspect
nimbus app schema > schema.graphqlThe proxy mounts the same Salesforce-compatible API surface that nimbus serve exposes, in-process rather than as a separate server. Everything the proxy does not recognise falls through to Vite — HMR socket, assets, source maps.
Full CRUD on /sobjects/<Object>, /query for SOQL, /search and /parameterizedSearch for SOSL. DML fires triggers and flows the way the real platform does, because it is the same interpreter that runs your tests.
POST /graphql serves cursor pagination, orderBy, nested where combinators, aggregates, and per-SObject Create/Update/Delete mutations. __schema is served from a model built at startup, so codegen runs against the local server instead of an org.
The REST UI API surface the SDK reaches for: object-info with picklists and record types, record defaults, list-ui and related lists, layouts, record actions. Enough for a production Multi-Framework app to render.
@RestResource classes are discovered and exposed under /services/apexrest/. POST /actions/custom/apex/<ClassName> dispatches to that class’s @InvocableMethod through the interpreter.
Composite, composite/batch, composite/tree, and multi-get, plus the full Bulk 2.0 ingest and query lifecycle. Nimbus processes bulk work synchronously because the runtime is in-process; the polling shape stays faithful.
/__nimbus/app/status returns JSON on the running server, /__nimbus/app/schema returns the live SDL, and /__nimbus/app/dashboard is an HTML view of requests as they arrive. Everything else falls through to Vite.
validFor bitmaps are empty, and the Tooling API, Reports, and Knowledge are absent. Unhandled paths return 501 with the path in the body, so you find out at the request rather than at deploy. The full list is in the CLI reference.UI code is hot. The bundle’s Vite dev server is the real one, spawned by Nimbus and proxied through untouched. Save a component and HMR patches the running app exactly as it does today — Nimbus is not in that path and does not slow it down.
Apex and metadata are loaded at startup. Nimbus reads your classes, triggers, flows, and object metadata when the server boots and holds them for the session. There is no file watcher on the Apex side: after changing a class or a field definition, restart nimbus app to pick it up. That restart is a local process boot against embedded PostgreSQL, not a deploy — seconds, not minutes, and it never touches an org.
So the honest shape of the loop is two speeds: instant for the UI, a restart for the backend. Both stay on your machine, which is the part that actually changed. If you want save-triggered feedback on the Apex itself, that is what watch mode is for — run it in a second terminal and let your tests police the backend while the app runs.
# Terminal 1 — the app
nimbus app
# nimbus app 127.0.0.1:4545
# bundle reactRecipes
# vite http://localhost:5173
# API version 66.0
# status /__nimbus/app/status
# Edit a .tsx file -> HMR, instant
# Edit a .cls file -> restart nimbus app
# Terminal 2 — the backend, on save
nimbus test:watch.nimbus/db, and the data your app reads is the data your tests and nimbus exec put there.Three local surfaces, one runtime underneath. Pick by what you are pointing at it.
nimbus serve exposes the same Salesforce-compatible REST surface — plus a gRPC Pub/Sub API — as a server on its own port, for SFDX, jsforce, integration tests, and anything else that speaks to an org. nimbus app mounts that surface behind the bundle proxy, so this is the data layer your app is talking to.
nimbus dev opens the Dev UI: test explorer, coverage, schema browser, Apex REPL. That is a browser interface Nimbus ships for working on your project. This page is the opposite direction — hosting the app you wrote. They run side by side.
Install the binary, point it at a Salesforce project, and run the test suite locally before you host anything. Five minutes, one command, no org required.
The bundle you already have, the Apex you already wrote, and a database that boots in seconds. nimbus app replaces the org in the loop and leaves everything else alone.