Expo & Metro setup
Two configuration changes are required. Both are small, and both are mandatory — skip either one and the model assets will not resolve at runtime.
1. Register the Expo config plugin
Add the package to the plugins array in app.json:
{
"expo": {
"plugins": ["@optimalai/react-native"]
}
}The plugin forwards to the ONNX Runtime app plugin, which wires the native module into your iOS and Android projects during prebuild.
2. Wrap the Metro config
Model files use extensions Metro does not treat as assets by default (.onnx, .bin, .txt). withOptimalAI registers them:
const { getDefaultConfig } = require("expo/metro-config");
const { withOptimalAI } = require("@optimalai/react-native/metro");
module.exports = withOptimalAI(getDefaultConfig(__dirname));Keep the withOptimalAI(...) call as the outermost wrapper so it sees the final config. If you already compose other wrappers, nest them inside:
module.exports = withOptimalAI(withSomethingElse(getDefaultConfig(__dirname)));3. Let Metro bundle the model assets
The generated optimalai-models.ts uses static require(...) calls. Metro resolves those to numeric asset IDs, and createTts turns those IDs into real file URIs at load time. For that to work in a release build, make sure the model directory is covered by your assetBundlePatterns:
{
"expo": {
"assetBundlePatterns": ["**/*"]
}
}Large installs
The Kokoro INT8 model is roughly 114 MB and voices.bin about 54 MB. Expect your development and release binaries to grow accordingly. Budget for it before you promise an app size.
4. Rebuild the Development Build
npx expo run:ios
npx expo run:androidExpo Go cannot load the SDK because it cannot host the native ONNX Runtime module. Any change to native dependencies requires a fresh build.
Verifying the setup
After a successful build, createTts({ ... }) followed by await tts.load() should reach ready:
const tts = createTts({ model: kokoroZhInt8, voiceId: "zf_001" });
await tts.load();
console.log(tts.status()); // "ready"If load() throws MODEL_NOT_FOUND, your Metro or asset config is the usual culprit — see the FAQ.