renmy app runs a Salesforce Multi-Framework UI bundle (a Vite-built React app) against the local renmy runtime. The API calls it makes land on a Salesforce Apex interpreter and an embedded PostgreSQL database on your machine. It needs no scratch org, sandbox, or internet connection.
renmy appRequires a Pro license. The command also answers to its legacy alias, renmy 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 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.
renmy 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, and it works offline.
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 renmy runtime 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
renmy 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.There are four steps, all on your machine. Nothing is stubbed; the runtime behind the proxy is the same one renmy test runs your Apex tests on.
renmy runtime 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 renmy app list to see exactly what renmy can find.
renmy runtime 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
renmy app
# Name one explicitly
renmy app reactRecipes
# Every bundle at once, pick what opens
renmy app --all --primary reactRecipes
# Attach to a vite you started yourself
renmy app reactRecipes --no-spawn
# Custom proxy address; skip the browser
renmy app --addr 127.0.0.1:4546 --no-open
# Install deps if node_modules/ is missing
renmy app --install
# See what renmy can find
renmy app list
# Codegen without an org to introspect
renmy app schema > schema.graphqlThe proxy mounts the same Salesforce-compatible API surface that renmy 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. renmy runtime 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.
nimbus.runtime.user does not apply here. Known gaps: picklist dependency 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 renmy and proxied through untouched. Save a component and HMR patches the running app exactly as it does today. renmy runtime is not in that path and does not slow it down.
Apex and metadata are loaded at startup. renmy runtime 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 renmy app to pick it up. That restart is a local process boot against embedded PostgreSQL, not a deploy. It takes seconds and never touches an org.
So there are two speeds: instant for the UI, a restart for the backend. Both stay on your machine. 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 check the backend while the app runs.
# Terminal 1 — the app
renmy app
# renmy 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 renmy app
# Terminal 2 — the backend, on save
renmy test:watch.nimbus/db, and the data your app reads is the data your tests and renmy exec put there.Three local surfaces share one runtime. Pick by what you are pointing at it.
renmy 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. renmy app mounts that surface behind the bundle proxy, so this is the data layer your app is talking to.
renmy dev opens the Dev UI: test explorer, coverage, schema browser, Apex REPL. That is a browser interface renmy runtime 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. It takes one command and about five minutes, with no org required.
The bundle you already have, the Apex you already wrote, and a database that boots in seconds. renmy app replaces the org and leaves everything else alone.