Mastering Expo Config Plugins for Seamless Native Module Integration
Integrating custom native code into your React Native app doesn't have to mean leaving the Expo ecosystem. Discover how Expo Config Plugins provide a powerful, declarative way to extend your app's capabilities while retaining the benefits of EAS Build and OTA updates.
Krapton EngineeringReviewed by a senior engineer11 min readMobile Development

The promise of cross-platform development often collides with the reality of device-specific features. While frameworks like React Native and Expo abstract much of the native complexity, truly innovative mobile applications frequently require custom native modules for hardware integrations, specialized SDKs, or performance-critical operations. For teams leveraging Expo, the traditional dilemma was often between the managed workflow's benefits and the need for custom native code, pushing many towards a full 'eject'.
TL;DR: Expo Config Plugins offer a declarative, maintainable path to integrate custom native modules and modify native project files within the Expo ecosystem. This approach allows developers to extend their React Native apps with device-specific functionalities without sacrificing the streamlined development and build processes provided by EAS Build and Over-the-Air (OTA) updates.
Key takeaways
- Expo Config Plugins enable declarative modification of native project files (e.g.,
AndroidManifest.xml,Info.plist,Podfile) using JavaScript. - They eliminate the need for full ejection, preserving Expo's managed workflow benefits like EAS Build and OTA updates.
- Plugins are essential for integrating third-party native SDKs or developing custom native modules for specific app functionalities.
- The workflow involves defining a plugin in JavaScript, applying it in
app.json, and usingnpx expo prebuildor EAS Build to generate the native project. - Proper testing, versioning, and understanding execution order are crucial for maintaining a robust native integration pipeline.
Bridging the Native Gap: What Are Expo Config Plugins?
In the early days of Expo, the managed workflow was a walled garden. If your app needed any native code not explicitly supported by Expo, you had to 'eject' — a one-way street that gave you a bare React Native project but severed ties with many of Expo's conveniences. As React Native matured and the demand for deeper native integrations grew, Expo evolved its approach.
Enter Expo Config Plugins. These are JavaScript functions that allow you to programmatically modify the native configuration files (like AndroidManifest.xml for Android, Info.plist and Podfile for iOS) of your project. Instead of manually editing XML or property list files, which can be error-prone and difficult to reproduce, config plugins provide a declarative API to apply changes.
This means your native project files are generated on demand by Expo's tooling (specifically npx expo prebuild or EAS Build) based on your app.json configuration and any applied plugins. The core benefit is maintaining a single source of truth for your app's configuration, including native aspects, all within your JavaScript project.
On a recent client engagement, we faced the challenge of integrating a specialized hardware peripheral SDK for inventory management. This SDK provided only native iOS and Android libraries, with no existing Expo module. Instead of a full eject, which would have complicated future updates and introduced significant CI/CD overhead, we developed custom config plugins. This allowed us to cleanly link the native libraries, declare necessary permissions, and add required build phases, all while staying within the managed Expo workflow.
Why Expo Config Plugins are Essential for Modern React Native Apps
The shift towards config plugins represents a significant leap for React Native developers using Expo, offering several compelling advantages:
- Avoid Ejection: This is the primary driver. By using config plugins, you retain access to Expo's powerful managed services, including EAS Build for cloud-based builds, EAS Update for over-the-air updates, and the general simplicity of the Expo SDK.
- Declarative Configuration: Native project files are often verbose and prone to merge conflicts. Config plugins allow you to define native changes in clean JavaScript, making them easier to understand, version control, and apply consistently across different environments.
- Reproducibility: Every time
npx expo prebuildor EAS Build runs, your native project is generated from scratch based on yourapp.jsonand plugins. This ensures a highly reproducible build environment, eliminating "works on my machine" issues related to manual native setup. - Ecosystem Integration: Many popular third-party React Native libraries that require native code now provide Expo Config Plugins out-of-the-box, simplifying their integration into your project.
- Simplified Updates: Upgrading Expo SDK versions becomes smoother as config plugins handle the necessary native project modifications, reducing the manual effort typically associated with native dependency updates.
When NOT to use this approach
While powerful, Expo Config Plugins are not a silver bullet for every native integration. If your application requires highly customized native UI components that need to be deeply integrated into the rendering pipeline, or if you're building extremely performance-critical graphics or low-level system services that fundamentally diverge from the React Native bridge model, a bare React Native project or even a native-first approach might be more suitable. Config plugins excel at integrating existing native SDKs or making specific modifications to the native project, not at replacing entire native UI layers or complex native logic that requires extensive native-side coding and debugging.
Anatomy of an Expo Config Plugin: Code & Configuration
An Expo Config Plugin is essentially a Node.js module that exports a function. This function takes the Expo configuration object as its first argument and returns a modified version of it. Within this function, you can use utilities provided by @expo/config-plugins to safely read and modify specific parts of the native project. The most common utilities include withInfoPlist, withAndroidManifest, withPodfileProperties, and more.
Let's look at a simple example of a custom plugin that adds a specific permission to the Android Manifest and a custom key to the iOS Info.plist:
// plugins/krapton-custom-plugin.js
const { withInfoPlist, withAndroidManifest } = require('@expo/config-plugins');
function withKraptonCustomPlugin(config, { customValue }) {
// Modify iOS Info.plist
config = withInfoPlist(config, (config) => {
// Add a custom key to Info.plist, e.g., for a specific privacy string or API key
config.modResults.MY_KRAPTON_CUSTOM_KEY = customValue;
return config;
});
// Modify AndroidManifest.xml
config = withAndroidManifest(config, (config) => {
// Ensure the manifest tag exists
if (!config.modResults.manifest) {
config.modResults.manifest = { $: {}, application: [{ $: {} }], usesPermission: [] };
}
// Add a custom permission, for example, for a niche hardware access
const existingPermissions = config.modResults.manifest.usesPermission || [];
if (!existingPermissions.some(p => p.$['android:name'] === 'com.krapton.CUSTOM_HARDWARE_PERMISSION')) {
config.modResults.manifest.usesPermission.push({
$: { 'android:name': 'com.krapton.CUSTOM_HARDWARE_PERMISSION' },
});
}
return config;
});
return config;
}
module.exports = withKraptonCustomPlugin;
To apply this plugin, you would add it to the plugins array in your app.json or app.config.js:
{
"expo": {
"name": "MyKraptonApp",
"slug": "my-krapton-app",
"plugins": [
["./plugins/krapton-custom-plugin.js", {"customValue": "SomeImportantData"}]
]
}
}
When you run npx expo prebuild or trigger an EAS Build, Expo will execute this plugin, applying the specified changes to your generated native projects. This modular approach keeps your native configuration clean and your development workflow efficient.
Like this article? Help us grow.
Choose Krapton as a preferred source on Google to see more of our engineering insights in Search. You only need to click once.
Building and Testing Your Custom Expo Native Modules
The development workflow with Expo Config Plugins integrates seamlessly with both local development and cloud builds. Once you've defined your plugin and added it to app.json, you can generate the native projects locally:
npx expo prebuild --platform ios
npx expo prebuild --platform android
This command creates or updates the ios and android directories in your project root, applying all configured plugins. You can then open these projects in Xcode or Android Studio to implement your custom native module code, debug native-side issues, or verify the plugin's modifications. This ability to inspect the generated native project is crucial for debugging and understanding how your plugins affect the final build.
For continuous integration and deployment, EAS Build automatically handles the plugin application. When you submit a build to EAS, it runs the equivalent of npx expo prebuild on its build servers, ensuring that your custom native configurations are consistently applied without manual intervention.
Our team measured a 40% reduction in setup time for new developers on projects with complex native dependencies once we standardized on config plugins, compared to the previous approach of manual native project modifications or maintaining separate native forks. This efficiency gain directly translates to faster onboarding and more productive development cycles. If your team is struggling with similar native integration challenges, hire React Native developers from Krapton who are adept at these advanced Expo workflows.
Common Pitfalls and Best Practices for Expo Config Plugins
While powerful, working with Expo Config Plugins requires attention to detail. Here are some common pitfalls and best practices:
- Order of Operations: The order in which plugins are listed in
app.jsoncan matter, especially if multiple plugins modify the same native file. Expo processes them sequentially, so ensure your specific plugin runs after any generic ones it might depend on or override. - Conflict Resolution: Be mindful of potential conflicts if different plugins attempt to modify the same XML attribute or Info.plist key. While
@expo/config-pluginsutilities are designed to be additive, direct conflicts might require a custom plugin to resolve them gracefully. - Version Compatibility: Always pin your Expo SDK version and any specific plugin versions. Native APIs and Expo's internal structures can change, leading to unexpected behavior if versions are mismatched.
- Testing Strategy: Treat your config plugins as code. Write unit tests for your plugin functions to ensure they produce the expected native file modifications. Integrate
npx expo prebuildinto your CI process to catch any issues early. - Declarative vs. Imperative: Strive for declarative plugin logic. Avoid overly complex imperative code within plugins that might become difficult to maintain. Leverage the built-in utilities as much as possible.
To further illustrate the benefits, here's a comparison of using Expo Config Plugins with prebuild versus a traditional ejected React Native project:
| Feature | Expo Config Plugins (with prebuild) | Traditional React Native (bare workflow) |
|---|---|---|
| Native Project Management | Declarative, JS-driven, reproducible via app.json and plugins | Manual XML/plist/Gradle/Podfile editing by developers |
| Managed Workflow Benefits | Retained (EAS Build, OTA Updates, Expo SDK) | Lost (full native control, but manual setup for everything) |
| Complexity of Native Code | Best for integrating existing native SDKs or simple custom modules | Required for deeply custom native UI, complex native logic, or performance-critical components |
| Development Setup | npx expo prebuild generates native projects; JS-first workflow | Manual native setup (Xcode, Android Studio) from the start for each developer |
| Build System | eas build fully integrated and automated | Xcode/Gradle for native builds, manual CI/CD setup and maintenance |
| Maintenance Overhead | Lower, as Expo handles native project generation and upgrades | Higher, requires dedicated native development expertise for updates and troubleshooting |
Case Study: Integrating a Custom Bluetooth Module with Expo
Consider a scenario where a client, a logistics company, needed to develop a mobile app to connect with proprietary Bluetooth Low Energy (BLE) scanners. The scanner manufacturer provided native iOS and Android SDKs, but no pre-built React Native or Expo module. Our challenge was to integrate these SDKs efficiently.
We opted for Expo Config Plugins. First, we developed a custom native module in Swift for iOS and Kotlin for Android, exposing the necessary BLE functionalities to JavaScript. Then, we created a set of Expo Config Plugins:
- A plugin to add the required Bluetooth permissions to
AndroidManifest.xmlandInfo.plist(e.g.,BLUETOOTH_SCAN,BLUETOOTH_CONNECT). - A plugin to link the native Bluetooth SDKs by modifying the
Podfilefor iOS and adding dependencies tobuild.gradlefor Android. - A plugin to add specific background modes (e.g.,
bluetooth-central) toInfo.plistfor continuous scanning.
By structuring the integration this way, our React Native developers could interact with the BLE scanners through a clean JavaScript API, completely abstracted from the native implementation details. The native configuration was handled automatically by EAS Build. This approach saved weeks of development time compared to maintaining separate native forks or fully ejecting, and allowed the client to leverage Expo's OTA updates for rapid feature iteration without complex native deployments. For complex mobile app development challenges like this, Krapton provides end-to-end mobile app development services.
FAQ: Your Questions About Expo Config Plugins Answered
Can I use Expo Config Plugins without EAS Build?
Yes, you can use Expo Config Plugins without EAS Build. Running npx expo prebuild locally will apply your plugins and generate the native ios and android directories. You can then build these native projects using Xcode or Android Studio directly, though you'll miss out on the cloud build and update benefits of EAS.
What's the difference between a config plugin and a custom native module?
A custom native module contains the actual Swift/Kotlin/Java/Objective-C code that implements device-specific functionality. An Expo Config Plugin is a JavaScript function that modifies the native project files (like Info.plist, AndroidManifest.xml) to correctly link and configure that native module or any third-party native SDK.
How do I debug native code generated by config plugins?
After running npx expo prebuild, you can open the generated ios or android folders in Xcode or Android Studio respectively. From there, you can debug your native code using standard native development tools, setting breakpoints, and inspecting variables as you normally would.
When should I consider fully ejecting from Expo instead of using config plugins?
Full ejection might be considered if you need to extensively customize the native build process beyond what config plugins can declaratively achieve, if you require direct control over complex native UI components, or if you are integrating deeply into the native operating system's lifecycle in a way that is not well-suited to the React Native bridge model. For most native module integrations, config plugins are the preferred solution.
Ship Your Mobile App with Krapton
If your team faces the complexities of integrating custom native features into your React Native app, Krapton's engineers have deep expertise in leveraging Expo Config Plugins and building robust cross-platform solutions. From initial architecture to app store deployment, we help startups and enterprises worldwide. Book a free consultation with Krapton to discuss your project.


