◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-04-08Advanced

Rork Max + EAS Build Custom Native Module Errors: Complete Fix Guide

Comprehensive guide to fixing EAS Build errors when adding custom native modules to Rork Max apps. Covers Config Plugin creation, iOS Podfile conflicts, Android Gradle errors, Hermes compatibility, and native initialization issues.

Rork Max236EAS Build22Native Modules5Config Plugin3iOS116Android55PodfileErrors

Rork Max + EAS Build Custom Native Module Errors: Complete Fix Guide

Rork Max works great for the vast majority of apps — but when you need something outside the Expo SDK (Bluetooth hardware, custom SDKs, native camera libraries), you're in EAS Build territory with custom native modules.

The first time through, it's a maze of Podfile conflicts, Execution failed for task ':app:merge...', and "the build passes but crashes immediately at launch." This guide maps out all the major failure modes with concrete fixes.

The Foundation: How EAS Build and Config Plugins Work

To add native code to a Managed Workflow Expo project (what Rork Max uses internally), you need Config Plugins. These are Node.js scripts that modify the native iOS and Android project files during npx expo prebuild — which EAS Build runs automatically before compiling.

Many libraries ship their own Config Plugin (like @config-plugins/react-native-ble-plx). For libraries that don't, you'll need to write one.

app.config.js plugins array
    ↓
npx expo prebuild (runs automatically in EAS Build)
    ↓
ios/ and android/ directories are generated
    ↓
iOS: pod install → Xcode build
Android: Gradle build
    ↓
Binary output

Error #1: Podfile Version Conflicts

Symptom: EAS Build fails with:

[!] CocoaPods could not find compatible versions for pod "SomeLibrary":
  In snapshot (Podfile.lock):
    SomeLibrary (= 1.2.0)
  In Podfile:
    SomeLibrary (~> 2.0)
// config-plugins/force-pod-version.js
const { withPodfile } = require('@expo/config-plugins');
 
module.exports = (config) => {
  return withPodfile(config, (modConfig) => {
    const { modResults } = modConfig;
    
    modResults.contents = modResults.contents.replace(
      /# Add any custom podfile content here/,
      `# Add any custom podfile content here
# Force version to resolve dependency conflict
pod 'SomeLibrary', '~> 2.0.0'`
    );
    
    return modConfig;
  });
};
// app.config.js
export default {
  expo: {
    plugins: ['./config-plugins/force-pod-version']
  }
};

Resetting the Podfile.lock:

npx expo prebuild --clean       # regenerate ios/ and android/ from scratch
cd ios && rm Podfile.lock       # remove the lock file
pod install --repo-update       # reinstall with updated pod specs
cd ..

Error #2: Config Plugin Not Applied — Module Crashes at Runtime

Symptom: Build succeeds, but app crashes with Native module cannot be null or Cannot read property of undefined.

This means the Config Plugin didn't write the native initialization code into the project. Verify your app.config.js plugins array:

export default {
  expo: {
    plugins: [
      // Wrong: library may not have a config plugin under this name
      'react-native-ble-plx',
      
      // Correct: check for @config-plugins namespace
      '@config-plugins/react-native-ble-plx',
      
      // Correct with options: pass config as array second element
      ['@config-plugins/react-native-ble-plx', {
        bluetoothAlwaysPermission: "$(PRODUCT_NAME) uses Bluetooth to connect to devices",
        bluetoothPeripheralPermission: "$(PRODUCT_NAME) uses Bluetooth to connect to peripheral devices"
      }],
    ]
  }
};

Verify Config Plugin application before building:

# Run prebuild to generate native files locally
npx expo prebuild --clean
 
# Check if iOS permissions were added
cat ios/MyApp/Info.plist | grep -A 2 "NSBluetooth"
 
# Check if Android permissions were added
cat android/app/src/main/AndroidManifest.xml | grep "BLUETOOTH"
 
# If these entries are missing, the Config Plugin didn't apply correctly

Error #3: Android Gradle Build Failures

Symptom: iOS builds fine but Android fails with errors like Execution failed for task ':app:mergeReleaseJavaResource' or Manifest merger failed.

// config-plugins/fix-android-build.js
 
const { withAppBuildGradle, withProjectBuildGradle } = require('@expo/config-plugins');
 
// Fix minSdkVersion when a library requires a higher minimum
const fixMinSdkVersion = (config) => {
  return withAppBuildGradle(config, (modConfig) => {
    modConfig.modResults.contents = modConfig.modResults.contents.replace(
      /minSdkVersion = \d+/,
      'minSdkVersion = 24'
    );
    return modConfig;
  });
};
 
// Fix Kotlin version conflicts
const fixKotlinVersion = (config) => {
  return withProjectBuildGradle(config, (modConfig) => {
    modConfig.modResults.contents = modConfig.modResults.contents.replace(
      /kotlinVersion = '.*'/,
      "kotlinVersion = '1.9.0'"
    );
    return modConfig;
  });
};
 
module.exports = (config) => {
  config = fixMinSdkVersion(config);
  config = fixKotlinVersion(config);
  return config;
};
// app.config.js
export default {
  expo: {
    plugins: ['./config-plugins/fix-android-build']
  }
};

Error #4: Hermes and New Architecture Incompatibility

Symptom: Module works in Expo Go (uses JSC) but crashes with Hermes, or Calling synchronous methods on native modules is not supported.

// app.config.js — control Hermes and New Architecture settings
export default {
  expo: {
    jsEngine: "hermes",   // default, keep this
    
    // If a module is incompatible with New Architecture, disable it here
    // (this is a temporary measure — prefer updating the library)
    newArchEnabled: false,
    
    ios: {
      newArchEnabled: false,
    },
    android: {
      newArchEnabled: false,
    }
  }
};

To check if a specific library supports Hermes and New Architecture, look for a react-native.config.js or check the library's GitHub issues for "Hermes" or "New Architecture" mentions.

Error #5: Missing Native Initialization Code

Symptom: Module is installed and Config Plugin exists, but at runtime you still get "module not found" errors.

Older React Native libraries that predate Config Plugins sometimes require manual entries in MainApplication.java (Android) and AppDelegate.mm (iOS). Config Plugins let you automate this:

// config-plugins/add-native-init.js
const { withMainApplication, withAppDelegate } = require('@expo/config-plugins');
 
// Android: add module to MainApplication.java
const withAndroidModule = (config) => {
  return withMainApplication(config, (modConfig) => {
    const { modResults } = modConfig;
    
    if (!modResults.contents.includes('import com.example.CustomModule;')) {
      // Add import
      modResults.contents = modResults.contents.replace(
        'import com.facebook.react.ReactApplication;',
        `import com.facebook.react.ReactApplication;
import com.example.CustomModule;`
      );
      
      // Add to packages list
      modResults.contents = modResults.contents.replace(
        'packages.add(new MainReactPackage());',
        `packages.add(new MainReactPackage());
packages.add(new CustomModule());`
      );
    }
    
    return modConfig;
  });
};
 
// iOS: add initialization to AppDelegate.mm
const withIOSInit = (config) => {
  return withAppDelegate(config, (modConfig) => {
    const { modResults } = modConfig;
    
    if (!modResults.contents.includes('#import <CustomSDK/CustomSDK.h>')) {
      modResults.contents = modResults.contents.replace(
        '#import <React/RCTBridge.h>',
        `#import <React/RCTBridge.h>
#import <CustomSDK/CustomSDK.h>`
      );
    }
    
    return modConfig;
  });
};
 
module.exports = (config) => {
  config = withAndroidModule(config);
  config = withIOSInit(config);
  return config;
};

Efficient Debug Strategy for EAS Build

EAS Build cycles are expensive (10–15 minutes per build). Minimize them with local verification first:

# Step 1: Verify prebuild output before EAS Build
npx expo prebuild --clean
# Inspect ios/ and android/ — check all expected changes are present
 
# Step 2: Test a local release build (requires macOS + Xcode for iOS)
npx expo run:ios --configuration Release
# or
npx expo run:android --variant release
 
# Step 3: Only after local validation, submit to EAS Build
eas build --profile preview --platform ios
 
# View build logs
eas build:list
eas build:view [BUILD_ID]

Multi-profile eas.json design:

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "ios": { "buildConfiguration": "Debug" }
    },
    "preview": {
      "distribution": "internal",
      "ios": { "buildConfiguration": "Release" },
      "env": { "API_URL": "https://staging.example.com" },
      "cache": {
        "key": "preview-cache-v1"
      }
    },
    "production": {
      "distribution": "store",
      "ios": { "buildConfiguration": "Release" },
      "env": { "API_URL": "https://api.example.com" }
    }
  }
}

Managing secrets securely:

# Store sensitive values as EAS Secrets (never commit these to git)
eas secret:create --scope project --name STRIPE_SECRET_KEY --value "sk_live_xxxxx"
eas secret:create --scope project --name FIREBASE_SERVICE_ACCOUNT --value "$(cat service-account.json)"
 
# List configured secrets
eas secret:list

Looking back

EAS Build custom native module errors fall into five main patterns:

  • Podfile conflicts → Use withPodfile Config Plugin to pin conflicting versions
  • Config Plugin not applied → Verify with npx expo prebuild --clean, check @config-plugins/ namespace
  • Android Gradle errors → Use withAppBuildGradle / withProjectBuildGradle to patch minSdkVersion and Kotlin version
  • Hermes incompatibility → Set newArchEnabled: false as a temporary workaround while waiting for library updates
  • Missing native initialization → Write custom withMainApplication / withAppDelegate plugins

The core diagnostic practice is: always run npx expo prebuild --clean and inspect the generated native files before submitting to EAS Build. Most issues are visible in the generated files, saving you multiple 15-minute build cycles.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-05-01
Implementing Screen Recording and Broadcast in Rork Max — A Production Guide for ReplayKit and MediaProjection
Integrate iOS ReplayKit and Android MediaProjection into your Rork Max app for gameplay recording, tutorial creation, and live broadcasting—covering the full production pipeline.
⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
⬡ Dev Tools2026-09-10
I Added a Dim Screen Button, and iPhones Stayed Dark After the App Was Closed
In expo-brightness, setBrightnessAsync applies only to the current activity on Android, but changes the device brightness itself on iOS. Here is why the cleanup burden falls on one platform only, and a hook that restores brightness through AppState.