SootSimConfig

defineConfig from sootsim/config — modules / turboModules / nativeModules slots and their ModuleResolution forms, env injection, settings overrides, initialState shape, and how the config travels to the runtime via URL query param.

The main configuration object for SootSim projects. Import from sootsim/config:

import { defineConfig } from 'sootsim/config'
export default defineConfig({
modules: { ... },
turboModules: { ... },
nativeModules: { ... },
env: { ... },
settings: { ... },
initialState: { ... },
})

defineConfig is just a typed pass-through — the file may also be plain CJS:

/** @type {import('sootsim/config').SootSimConfig} */
module.exports = { env: { ... } }

Module slots

There are three separate slots because the runtime resolves each differently.

modules

Record<string, ModuleResolution> — overrides for any Metro module by path fragment. The key is matched against the Metro module name as a path fragment, not a strict package name. A key like react-native-widgetkit matches …/node_modules/react-native-widgetkit/index.js; a key like dist/assets/config.json matches a deep file path inside a module.

Keys are sorted longest-first, so subpath keys can override package-level keys.

turboModules

Record<string, NativeModuleResolution> — modules served via the TurboModuleRegistry surface. Keyed by the TurboModule name the guest bundle requests (e.g. RNCAsyncStorage), not by JS module path.

nativeModules

Record<string, NativeModuleResolution> — modules served via the legacy NativeModules surface. Keyed by the native module name the guest bundle requests (e.g. RNKeychainManager).

ModuleResolution kinds

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

turboModules and nativeModules additionally accept a direct native-module object (any shape) for non-URL configs:

type NativeModuleResolution = ModuleResolution | Record<string, any>

env

Record<string, string> — environment variables installed as process.env.* inside the guest runtime.

Caveat: many React Native projects read env vars at bundle time via Babel plugins (react-native-dotenv, Expo’s EXPO_PUBLIC_* inliner, …). Those values are already baked into the bundle that Metro served. env here only sets process.env.* for code that reads env at runtime — code that already got its values inlined at bundle time will not see new values. To change those, rebuild the bundle with the new env and reload.

settings

Partial<SettingsValues> — simulator settings overrides (device model, appearance, network, locale, chrome). See settings schema.

initialState

Initial app state passed to the simulator:

{
authenticated?: boolean
locale?: string
colorScheme?: 'light' | 'dark' | 'auto'
featureFlags?: Record<string, boolean | string | number>
[key: string]: any
}

How the config reaches the runtime

The config travels as the ?sootsimConfig=<json> URL query param.

  • applySootSimConfigToUrl(url, config) serializes into the param.
  • readSootSimConfigFromSearchParams(searchParams) deserializes inside the engine.
  • ExternalApp preloads any { file: '…' } modules by fetching them through the /__sootsim-replacement-module dev-server middleware (project-local source compiled by esbuild, restricted to react / react-native imports).
  • bundle-loader.ts matches modules keys against every __d() factory registration and serves the configured resolution instead.

mergeSootSimConfig(base, override) shallowly merges two configs by slot.

Ready to build?

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

npm i -g sootsim