What Are Config Plugins and When to Use Them
Understand how Expo's prebuild pipeline transforms app.json into native iOS and Android projects, and identify when a config plugin is needed to add a third-party native library.
What Are Config Plugins and When to Use Them is a free React Native Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the React Native Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Expo Managed Workflow and Native Files
In the Expo managed workflow, you never manually edit the ios/ or android/ folders. Instead, Expo's prebuild pipeline generates those native projects from app.json and your JavaScript dependencies. This is powerful for keeping native files in sync, but it creates a challenge: how do you customize native settings for third-party libraries that require specific native code changes?
What Is a Config Plugin
A Config Plugin is a JavaScript function that receives the current Expo app configuration object and returns a modified version of it. Expo's prebuild step applies all config plugins in sequence before generating native iOS and Android projects. Config plugins let you add permissions, modify build settings, add native files, and configure manifests — all from JavaScript, without touching native code directly.
// Minimal config plugin shape
const { withInfoPlist } = require('@expo/config-plugins');
module.exports = function withMyPlugin(config) {
// Receive config, return modified config
return withInfoPlist(config, (iosConfig) => {
iosConfig.modResults['NSCameraUsageDescription'] =
'We need camera access to scan QR codes.';
return iosConfig;
});
};When Do You Need a Config Plugin
You need a config plugin when a native library requires changes that app.json alone cannot express. Common examples include: adding a permission string to Info.plist or AndroidManifest, adding a service or receiver declaration, modifying build.gradle dependencies, copying native asset files into the iOS or Android project, or setting custom URL schemes for OAuth. If a library's README says 'Add this to Info.plist', you need a plugin.
// app.json — referencing plugins
{
'expo': {
'name': 'MyApp',
'plugins': [
'expo-camera',
'expo-location',
['./plugins/withStripePlugin', { 'merchantId': 'merchant.com.myapp' }],
'@react-native-google-signin/google-signin'
]
}
}The Prebuild Pipeline Step by Step
When you run npx expo prebuild, Expo performs these steps: (1) reads app.json, (2) resolves all plugins listed in the plugins array, (3) applies each plugin's transform function to the config in order, (4) writes the final ios/ and android/ directories based on the transformed config. The generated native files replace the previous ones — meaning manual edits to native files are overwritten on every prebuild.
# Run prebuild (regenerates ios/ and android/)
npx expo prebuild
# Prebuild for a specific platform
npx expo prebuild --platform ios
npx expo prebuild --platform android
# Clean existing native dirs first (fresh generation)
npx expo prebuild --cleanThe @expo/config-plugins Package
Expo provides the @expo/config-plugins package with a set of helper functions called mods (modifiers). Each mod targets a specific native file: withInfoPlist for iOS Info.plist, withAndroidManifest for AndroidManifest.xml, withGradleProperties for Android properties, withXcodeProject for the Xcode project file, and more. You compose mods to build up complex native configurations.
const {
withInfoPlist,
withAndroidManifest,
withGradleProperties,
withEntitlementsPlist,
} = require('@expo/config-plugins');
// Each mod receives (config, modifier function)
// The modifier receives the parsed native file, modifies it, returns it
module.exports = function withAllPlatforms(config) {
config = withInfoPlist(config, (c) => { /* iOS */ return c; });
config = withAndroidManifest(config, (c) => { /* Android */ return c; });
return config;
};Plugin Composition with withPlugins
If your plugin needs to apply multiple mods, use withPlugins to compose them cleanly. Pass it an array of [pluginFunction, options] tuples and it applies them in sequence. This makes complex plugins readable and avoids deeply nested function calls. Large config plugins (like one for a full payment SDK) often use withPlugins internally to organize their multiple native file modifications.
const { withPlugins } = require('@expo/config-plugins');
const { withCameraPermission } = require('./withCameraPermission');
const { withGoogleServicesFile } = require('./withGoogleServicesFile');
const { withFirebaseAndroid } = require('./withFirebaseAndroid');
module.exports = function withFirebasePlugin(config, options) {
return withPlugins(config, [
[withCameraPermission, { text: options.cameraPermissionText }],
withGoogleServicesFile,
withFirebaseAndroid,
]);
};Plugins Bundled with Libraries
Many popular React Native libraries ship their own config plugin as the app.plugin.js file at their package root. When you add such a library to the plugins array in app.json, Expo auto-discovers and runs this file during prebuild. Libraries like expo-camera, expo-notifications, react-native-maps, and @stripe/stripe-react-native all use this approach.
// node_modules/expo-camera/app.plugin.js (simplified)
const { withPermissions } = require('@expo/config-plugins');
module.exports = function withCamera(config) {
return withPermissions(config, [
'android.permission.CAMERA',
'android.permission.RECORD_AUDIO',
]);
};
// app.json just needs:
// 'plugins': ['expo-camera']Custom Plugin vs Bare Workflow
Before writing a custom config plugin, ask: could I switch to a bare workflow instead? In a bare workflow you eject from Expo managed and edit native files directly. Config plugins are better when you want to keep the managed workflow (easier OTA updates, no native IDE required, cloud builds). If you frequently need deep native customization, a bare workflow (or Expo's hybrid bare + config-plugins approach) may be more practical.
Inspecting Prebuild Output
After running expo prebuild, open the generated ios/ and android/ folders to verify your plugin worked. Check ios/MyApp/Info.plist for iOS changes and android/app/src/main/AndroidManifest.xml for Android changes. You can also run expo prebuild --no-install to skip pod install and inspect quickly. Add assertions in your plugin that throw if a required key is missing to catch misconfigurations early.
# After prebuild, check AndroidManifest
grep -A2 'CAMERA' android/app/src/main/AndroidManifest.xml
# Expected: <uses-permission android:name='android.permission.CAMERA'/>
# Check Info.plist on iOS
grep -A1 'NSCamera' ios/MyApp/Info.plist
# Expected:
# <key>NSCameraUsageDescription</key>
# <string>We need camera access...</string>Config Plugin Error Handling
Config plugins run at build time, so errors appear during expo prebuild rather than at runtime. If a plugin throws, prebuild fails with a descriptive stack trace showing which plugin and which step failed. Always validate your plugin inputs (e.g., check that a required API key option was provided) and throw with a clear error message so developers know exactly what to fix in their app.json.
module.exports = function withStripe(config, options = {}) {
if (!options.merchantId) {
throw new Error(
'withStripe: You must provide a merchantId option in app.json. ' +
'Example: ["./plugins/withStripe", { "merchantId": "merchant.com.yourapp" }]'
);
}
return withInfoPlist(config, (c) => {
c.modResults['StripePublishableKey'] = options.publishableKey;
return c;
});
};Config Plugins vs app.json Fields
Many common native settings are already supported directly in app.json without needing a plugin — things like icon, splash, permissions (for Expo bare), backgroundColor, and orientation. Only reach for a config plugin when you need to modify something that app.json has no built-in field for. Check the Expo app.json reference first before writing a custom plugin to avoid reinventing existing functionality.
// app.json built-in fields (no plugin needed)
{
'expo': {
'icon': './assets/icon.png',
'splash': { 'image': './assets/splash.png' },
'ios': {
'bundleIdentifier': 'com.yourapp',
'infoPlist': {
'NSPhotoLibraryUsageDescription': 'Save your photos'
// ^ built-in way to set Info.plist keys
}
}
}
}Quick Check
Test your understanding of React Native Mobile Development concepts from this lesson.
Lesson Recap
In this lesson you learned: why config plugins exist as the managed-workflow alternative to editing native files, how Expo's prebuild pipeline applies plugins to generate native projects, and how libraries ship their own app.plugin.js files for automatic native configuration. You also saw how to use @expo/config-plugins helpers like withInfoPlist and withAndroidManifest. Next up we write our first config plugin from scratch.
Frequently asked questions
Is the “What Are Config Plugins and When to Use Them” lesson free?
Yes — the full text of “What Are Config Plugins and When to Use Them” is free to read here on the web, and the React Native Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Native Academy course, upgrade to CoddyKit PRO.
What will I learn in “What Are Config Plugins and When to Use Them”?
Understand how Expo's prebuild pipeline transforms app.json into native iOS and Android projects, and identify when a config plugin is needed to add a third-party native library. You practise React Native Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start React Native Academy?
No prior experience is required. React Native Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “What Are Config Plugins and When to Use Them” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this React Native Academy lesson?
Yes. Every React Native Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- What Are Config Plugins and When to Use Them
- Writing Your First Config Plugin
- Modifying AndroidManifest and Info.plist
- Distributing Config Plugins as npm Packages