Writing Custom Stubs
Author project-local stubs via sootsim.config — noop for empty modules, inline for static-value modules, file for real implementations backed by browser APIs, and turboModules / nativeModules for the TurboModule and NativeModules surfaces. Pure JS packages should never be stubbed.
When SootSim doesn’t have a builtin stub for a package you use, you can
provide your own through sootsim.config.ts. Three slots, picked by what
the guest bundle actually requests:
modules— generic Metro module overrides (matched by path fragment)turboModules— TurboModuleRegistry name overridesnativeModules— legacy NativeModules name overrides
See Configuration for the full surface.
The golden rule — only stub native modules
Never stub a pure JS package. SootSim simulates React Native well enough
that pure-JS libraries (even when named react-native-*) should just work
from the bundle. Composing View, Text, TouchableOpacity, etc. is
already supported.
Before authoring a stub, verify the package has native code:
NativeModules.X,TurboModuleRegistry.get('X'), orrequireNativeComponent('X')somewhere in its JSios/orandroid/directory with ObjC/Swift/Java/Kotlin sources- A
.podspecorbuild.gradlewith native dependencies
Packages that need stubs: react-native-mmkv, react-native-keychain,
@react-native-async-storage/async-storage, expo-haptics,
react-native-fs, etc.
Packages that do not need stubs: @gorhom/bottom-sheet (pure JS on
gesture-handler + reanimated), @gorhom/portal (pure JS context),
react-native-gifted-chat, react-native-calendars — any pure-JS UI
library. If a pure-JS package doesn’t render correctly under SootSim, the
fix is in SootSim’s base RN implementation, not in a stub.
modules — generic JS module overrides
Noop (empty module)
For packages you don’t need in the simulator:
Inline (static value)
For config-style packages whose entire surface is data:
Inline also works for deep file paths inside a package — keys match the Metro module name as a path fragment, so this overrides one file:
File (real implementation, browser-backed)
For packages that need real behavior, point at a project-local file. The dev server compiles it with esbuild on demand:
File-stub constraints (enforced by the dev middleware):
- Allowed static imports: only
reactandreact-native - No runtime
require()calls .js/.jsx/.ts/.tsxonly
If you need a third-party JS lib inside a stub, vendor the relevant code into the file or replace it with browser-native APIs.
Use (redirect to an existing compat stub)
If a package’s native surface matches another package SootSim already stubs, redirect rather than re-implementing:
false (disable SootSim’s builtin stub)
When the upstream package’s JS is correct on its own and you only need SootSim to stop redirecting it to a builtin stub:
turboModules and nativeModules — native-seam overrides
If the guest bundle reaches the native side via
TurboModuleRegistry.get('Foo') or NativeModules.Foo, the JS-side
modules slot doesn’t help — you need the native-seam slot.
Mattermost is the canonical example, mixing both:
Both slots accept the same ModuleResolution kinds ('noop', false,
{ use }, { file }, { inline }) plus a direct
Record<string, any> native-module object for non-URL configs.
Authoring guidelines
- Export the same named exports as the original package (matching its type signatures). If the package’s default export is a function, your stub’s default export should be a function too — the bundle loader preserves callable default exports.
- Use real browser APIs where they exist: camera →
getUserMedia, clipboard →navigator.clipboard, file picker →<input type="file">, haptics →navigator.vibrate. The goal is real behavior, not a noop. - For UI components, return a
View/Textwrapper that renderschildrenrather thannull— most callers depend on layout being preserved. - For hooks, return sensible defaults that satisfy the type but don’t
pretend the native side is doing real work (e.g. permission hooks should
return
grantedonly if the browser actually granted the analogous permission). - Cite the upstream source you matched. SootSim’s compat philosophy is “study upstream, mirror exactly” — comments referencing the real package’s file/line make later updates safe.
- Never stub a pure-JS package. If it doesn’t render, fix SootSim’s base RN implementation instead.
Related
- Configuration guide — full
sootsim.config.tssurface with demo-app examples - SootSimConfig API — type reference
- Compat overview — which packages already have builtin stubs