Configuration
sootsim.config.ts (or .js) is the per-project, repo-rooted config file
that describes how SootSim should load your app: which native modules to
stub, which env vars to inject, which device/appearance settings to default
to, any initial app state, and whether this repo needs a fixed engine version.
Create one
Create sootsim.config.ts in the repo root only when you need a fixed runtime
or advanced module, environment, or simulator overrides. It is not required
for normal sootsim open <port> usage.
From a monorepo root, SootSim reads package.json#workspaces and automatically
uses the config for the only React Native app it finds. It never guesses when
multiple apps match. The fallback scan is shallow, time-limited, and skips
dependencies, build output, native projects, fixtures, examples, dot
directories, and symlinks.
The smallest version selection has no import and does not add SootSim to the app’s dependencies:
Remove the field to follow the current stable runtime. Repos that already have
sootsim as a dependency may optionally use defineConfig for editor types,
but the global CLI does not require a per-app install.
Full reference
How module / turboModule / nativeModule differ
Three separate slots because the runtime resolves each differently:
modules: overrides for any Metro module by path fragment. Keys are matched against the Metro module name (e.g.…/node_modules/react-native-widgetkit/index.js); a key likereact-native-widgetkitmatches that path. Keys are tried longest-first, so subpath keys override package-level keys.turboModules: keyed by the TurboModule name the guest bundle requests (RNCAsyncStorage,RNFastImageView, …), served through SootSim’s TurboModuleRegistry surface.nativeModules: keyed by the legacy NativeModules name (RNKeychainManager,RNUtils, …), served through theNativeModules.<Name>surface.
If a guest bundle reads TurboModuleRegistry.get('Foo') and you have a
Foo key under modules, it will not match: modules is for JS-module
paths, turboModules is for TurboModule names.
ModuleResolution kinds
turboModules and nativeModules additionally accept a direct
Record<string, any> for non-URL configs.
Examples
Simple noop list
Silence native modules that are irrelevant in the simulator (analytics, IDFA, widgets, in-app updates):
Env only, CJS form
nativeModules + deep inline override
Some apps read config from a JSON file deep inside their own bundle and ship several legacy NativeModules. You can override a deep file by path fragment and seam the native managers:
The three slots compose freely: redirect JS modules by path, seam TurboModules and legacy NativeModules by name, and inline static config a package reads from its own bundle.
Env caveat: bundle-time vs runtime
Many React Native projects read env vars at bundle time via Babel plugins
(react-native-dotenv, Expo’s EXPO_PUBLIC_* inliner, babel-plugin-transform-inline-environment-variables).
Those values are already inlined into the bundle Metro served, so env in
sootsim.config.ts only sets process.env.* for code that reads env at
runtime.
If your code looks like process.env.API_URL after Metro and that string
has been replaced by a literal in the bundle, changing env here does
nothing. Rebuild the bundle with the new env (restart Metro with the new
.env, or set the var in the Metro process) and reload. Bluesky and
Uniswap’s configs both document this constraint.
How the config reaches the runtime
The CLI carries your config to the engine when it opens your app, and the engine applies the module / turbo / native overrides before any guest module factory runs. Env vars that were already inlined at bundle time can’t be changed here (see the caveat above).
runtimeVersion is handled by the local host before the engine loads. SootSim
installs that runtime on demand and serves the app from a version-specific
localhost origin. This selection applies only to that repo, so another app can
follow stable or select a different version at the same time. Remove the field
to follow stable again.
For a one-off module swap without writing a config file, pass it inline:
terminal
sootsim maestro test accepts the same --replace <module>=<file> flag, so a test run
can stub a module for just that run.
Settings reference
Settings can be configured three ways, in order of override priority (highest last):
sootsim.config.ts: project-level defaults- Simulator UI: runtime changes (notification center pull-down)
- CLI flags: per-invocation overrides
Device
| setting | CLI flag | values | default |
|---|---|---|---|
| deviceModel | --device | iphone-se, iphone-16, …, iphone-17-pro-max | iphone-17-pro |
| orientation | --orientation | portrait, landscape | portrait |
Appearance
| setting | CLI flag | values | default |
|---|---|---|---|
| colorScheme | --theme | light, dark, auto | auto |
| reduceMotion | none | boolean | false |
| boldText | none | boolean | false |
| fontSize | none | 0.5–2.0 | 1.0 |
Network
| setting | CLI flag | values | default |
|---|---|---|---|
| networkCondition | --network | wifi, lte, fast-3g, slow-3g, offline | wifi |
Locale
| setting | CLI flag | values | default |
|---|---|---|---|
| language | --language | ISO 639-1 codes | en |
| region | --region | ISO 3166-1 codes | US |
Chrome
| setting | CLI flag | values | default |
|---|---|---|---|
| showFrame | --frame | boolean | true |
| showTouches | none | boolean | false |
| showStatusBar | none | boolean | true |
| showHomeIndicator | none | boolean | true |
| a11yMode | none | off, delayed, active | delayed |
| inspectMode | none | boolean | false |
Optional dev-server integration
If you want a stable /__contrast URL on the same dev server your team already
runs, install the integration that matches your stack:
- Metro Plugin for Expo, bare React Native, and any Metro app
- One Plugin for One apps
These integrations expose SootSim on the existing dev server but do not
auto-load sootsim.config.ts, so apply module overrides with --replace (see
“How the config reaches the runtime” above) when driving through a plugin.
