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, and any initial app state.

Create one

Create sootsim.config.ts in the repo root only when you need advanced module, environment, or simulator overrides. It is not required for normal sootsim open <port> usage.

Both forms are accepted:

// sootsim.config.ts: typed via defineConfig
import { defineConfig } from 'sootsim/config'
export default defineConfig({
/* … */
})
// sootsim.config.js: plain CJS, type-annotated via JSDoc
/** @type {import('sootsim/config').SootSimConfig} */
module.exports = {
/* … */
}

Full reference

import { defineConfig } from 'sootsim/config'
export default defineConfig({
// generic Metro module overrides (matched by path fragment).
modules: {
'react-native-analytics': 'noop', // empty module
'react-native-gesture-handler': false, // disable SootSim's builtin, use original
'my-native-lib': { use: 'expo-haptics' }, // redirect to an existing compat stub
'my-native-camera': { file: './stubs/camera.ts' },
'react-native-config': {
inline: { API_URL: 'http://localhost:3000', ENV: 'development' },
},
// path-fragment match: this targets a deep file inside a package
'dist/assets/config.json': {
inline: {
/* … */
},
},
},
// TurboModuleRegistry surface (keyed by TurboModule name).
turboModules: {
RNCAsyncStorage: { file: './stubs/async-storage.ts' },
},
// legacy NativeModules surface (keyed by native module name).
nativeModules: {
RNKeychainManager: { file: './stubs/keychain.ts' },
},
// env vars (process.env.*), see "Env caveat" below.
env: { API_URL: 'http://localhost:3000', DEBUG: 'true' },
// simulator settings (CLI flags override these per invocation).
settings: { deviceModel: 'iphone-16-pro', colorScheme: 'dark' },
// initial app state passed to the simulator.
initialState: {
authenticated: false,
locale: 'en',
featureFlags: { newOnboarding: true },
},
})

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 like react-native-widgetkit matches 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 the NativeModules.<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

type ModuleResolution =
| 'noop' // empty module
| false // disable SootSim's builtin stub, use the original
| { use: string } // redirect to an existing compat stub
| { file: string } // load a project-local file
| { inline: Record<string, any> } // inline static object as module exports

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):

import { defineConfig } from 'sootsim/config'
export default defineConfig({
// env vars the app reads via react-native-dotenv babel plugin.
// these are injected at bundle time, not runtime, so they're only
// useful if sootsim re-bundles. included here for documentation.
env: {
NODE_ENV: 'development',
EXPO_BUILD_CONFIGURATION: 'Debug',
},
modules: {
'@datadog/mobile-react-navigation': 'noop', // analytics, irrelevant in sim
'@shopify/react-native-performance-navigation': 'noop',
'@sparkfabrik/react-native-idfa-aaid': 'noop', // IDFA, irrelevant in browser
'react-native-widgetkit': 'noop', // iOS widgets
'sp-react-native-in-app-updates': 'noop', // in-app updates
},
})

Env only, CJS form

/** @type {import('sootsim/config').SootSimConfig} */
module.exports = {
env: {
EXPO_PUBLIC_ENV: 'development',
EXPO_PUBLIC_LOG_LEVEL: 'debug',
EXPO_PUBLIC_LOG_DEBUG: 'session',
},
}

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:

defineConfig({
modules: {
// mattermost patches react-native-keychain's JS entry itself; let it run
// and only provide the native manager seam below.
'react-native-keychain': false,
// override a deep file inside the bundle by path-fragment match.
'dist/assets/config.json': {
inline: {
DefaultServerUrl: 'http://localhost:8065',
DefaultServerName: 'Mattermost Demo',
AutoSelectServerUrl: true,
SentryEnabled: false,
// …
},
},
},
nativeModules: {
RNKeychainManager: { file: './stubs/mattermost/keychain.ts' },
RNUtils: { file: './stubs/mattermost/rn-utils.ts' },
GenericClient: { file: './stubs/mattermost/network-client.ts' },
ApiClient: { file: './stubs/mattermost/network-client.ts' },
WebSocketClient: { file: './stubs/mattermost/network-client.ts' },
},
})

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).

For a one-off module swap without writing a config file, pass it inline:

terminal

sootsim open <port> --replace react-native-camera=./stubs/camera.ts

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):

  1. sootsim.config.ts: project-level defaults
  2. Simulator UI: runtime changes (notification center pull-down)
  3. CLI flags: per-invocation overrides

Device

settingCLI flagvaluesdefault
deviceModel--deviceiphone-se, iphone-16, …, iphone-17-pro-maxiphone-17-pro
orientation--orientationportrait, landscapeportrait

Appearance

settingCLI flagvaluesdefault
colorScheme--themelight, dark, autoauto
reduceMotionnonebooleanfalse
boldTextnonebooleanfalse
fontSizenone0.5–2.01.0

Network

settingCLI flagvaluesdefault
networkCondition--networkwifi, lte, fast-3g, slow-3g, offlinewifi

Locale

settingCLI flagvaluesdefault
language--languageISO 639-1 codesen
region--regionISO 3166-1 codesUS

Chrome

settingCLI flagvaluesdefault
showFrame--framebooleantrue
showTouchesnonebooleanfalse
showStatusBarnonebooleantrue
showHomeIndicatornonebooleantrue
a11yModenoneoff, delayed, activedelayed
inspectModenonebooleanfalse

Optional dev-server integration

If you want a stable /__soot URL on the same dev server your team already runs, install the integration that matches your stack:

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.

Ready to build?

Run your React Native app in the browser. No simulators, no native toolchain, no waiting.

curl -fsSL https://sootsim.com/install.sh | sh