File: mods.md | Updated: 11/15/2025
Hide navigation
Search
Ctrl K
Home Guides EAS Reference Learn
Archive Expo Snack Discord and Forums Newsletter
Copy page
Learn about mods and how to use them when creating a config plugin.
Copy page
This guide explains what mods and mod plugins are, how they work, and how to use them effectively when creating config plugins for your Expo project.
Using the diagram below, in this guide, you will learn the last two parts of the config plugin hierarchy:
withMyPlugin
"myPlugin"
Config Plugin
withAndroidPlugin
withIosPlugin
Plugin Function
withAndroidManifest
withInfoPlist
Mod Plugin Function
mods.android.manifest
mods.ios.infoplist
Mod
Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.
Mod plugins provide a way to modify native project files during the prebuild process. They are made available from expo/config-plugins library and wrap top-level mods (also known as _default mods
_) because top-level mods are platform-specific and perform various tasks that can be difficult to understand at first.
Tip: If you are developing a feature that requires mods, you should use mod plugins instead of interacting with top-level mods directly.
The following mod plugins are available in the expo/config-plugins library:
| Default Android mod | Mod plugin | Dangerous | Description |
| --- | --- | --- | --- |
| mods.android.manifest | withAndroidManifest (Example<br>) | - | Modify the android/app/src/main/AndroidManifest.xml as JSON (parsed with xml2js<br>) |
| mods.android.strings | withStringsXml (Example<br>) | - | Modify the android/app/src/main/res/values/strings.xml as JSON (parsed with xml2js<br>). |
| mods.android.colors | withAndroidColors (Example<br>) | - | Modify the android/app/src/main/res/values/colors.xml as JSON (parsed with xml2js<br>). |
| mods.android.colorsNight | withAndroidColorsNight (Example<br>) | - | Modify the android/app/src/main/res/values-night/colors.xml as JSON (parsed with xml2js<br>). |
| mods.android.styles | withAndroidStyles (Example<br>) | - | Modify the android/app/src/main/res/values/styles.xml as JSON (parsed with xml2js<br>). |
| mods.android.gradleProperties | withGradleProperties (Example<br>) | - | Modify the android/gradle.properties as a Properties.PropertiesItem[]. |
| mods.android.mainActivity | withMainActivity (Example<br>) | | Modify the android/app/src/main/<package>/MainActivity.java as a string. |
| mods.android.mainApplication | withMainApplication (Example<br>) | | Modify the android/app/src/main/<package>/MainApplication.java as a string. |
| mods.android.appBuildGradle | withAppBuildGradle (Example<br>) | | Modify the android/app/build.gradle as a string. |
| mods.android.projectBuildGradle | withProjectBuildGradle (Example<br>) | | Modify the android/build.gradle as a string. |
| mods.android.settingsGradle | withSettingsGradle (Example<br>) | | Modify the android/settings.gradle as a string. |
| Default iOS mod | Mod plugin | Dangerous | Description |
| --- | --- | --- | --- |
| mods.ios.infoPlist | withInfoPlist (Example<br>) | - | Modify the ios/<name>/Info.plist as JSON (parsed with @expo/plist<br>). |
| mods.ios.entitlements | withEntitlementsPlist (Example<br>) | - | Modify the ios/<name>/<product-name>.entitlements as JSON (parsed with @expo/plist<br>). |
| mods.ios.expoPlist | withExpoPlist (Example<br>) | - | Modify the ios/<name>/Expo.plist as JSON (Expo updates config for iOS) (parsed with @expo/plist<br>). |
| mods.ios.xcodeproj | withXcodeProject (Example<br>) | - | Modify the ios/<name>.xcodeproj as an XcodeProject object (parsed with xcode<br>). |
| mods.ios.podfile | withPodfile (Example | - | Modify the ios/Podfile as a string. |
| mods.ios.podfileProperties | withPodfileProperties (Example<br>) | - | Modify the ios/Podfile.properties.json as JSON. |
| mods.ios.appDelegate | withAppDelegate (Example<br>) | | Modify the ios/<name>/AppDelegate.m as a string. |
Note about default Android and iOS mods:
Default mods are provided by the mod compiler for common file manipulation. Dangerous modifications rely on regular expressions (regex) to modify application code, which may cause the build to break. Regex mods are also difficult to version, and therefore should be used sparingly. Always opt towards using application code to modify application code, that is, Expo Modules native API.
Config plugins use mods (short for modifiers) to modify native project files during the prebuild process. Mods are asynchronous functions that allow you to make changes to platform-specific files such as AndroidManifest.xml and Info.plist, and other native configuration files without having to manually edit them. They execute only during the syncing phase of npx expo prebuild (prebuild process).
They accept a config and a data object, then modify and return both of them as a single object. For example, in native projects, mods.android.manifest modifies AndroidManifest.xml and mods.ios.plist modifies Info.plist.
You don't use mods as top-level functions (for example with.android.manifest) directly in your config plugin. When you need to use a mod, you use mod plugins in your config plugins. These mod plugins are provided by the expo/config-plugins library and wrap top-level mod functions and behind the scenes they perform various tasks. To see a list of available mods, check out the mod plugins provided by expo/config-plugins
.
How default mods work and their key characteristics
When a default mod resolves, it is added to the mods object of the app config. This mods object is different from the rest of the app config because it doesn't get serialized, which means you can use it to perform actions during code generation. Whenever possible, you should use available mod plugins instead of default mods since they are easier to work with.
Here is a high-level overview of how default mods work:
getPrebuildConfig
from @expo/prebuild-configwithIosExpoPlugins. This includes name, version, icons, locales, and so on.compileModsAsyncmods.ios.infoPlist), then writing the results to the file systemprojectRoot
Here are some key characteristics of default mods:
mods are omitted from the manifest and cannot be accessed via Updates.manifest. Mods exist for the sole purpose of modifying native project files during code generation!
mods can be used to read and write files safely during the npx expo prebuild command. This is how Expo CLI modifies the Info.plist, entitlements, xcproj, and so on.
mods are platform-specific and should always be added to a platform-specific object:
app.config.ts
Copy
module.exports = { name: 'my-app', mods: { ios: { /* iOS mods... */ }, android: { /* Android mods... */ }, }, };
After mods are resolved, the contents of each mod will be written to disk. Custom mods can be added to support new native files. For example, you can create a mod to support the GoogleServices-Info.plist, and pass it to other mods.
When a mod plugin is executed, it gets passed a config object with additional properties: modResults and modRequest.
modResultsThe modResults object contains the data to modify and return. Its type depends on the mod that's being used.
modRequestThe modRequest object contains the following additional properties supplied by the mod compiler.
| Property | Type | Description |
| --- | --- | --- |
| projectRoot | string | Project root directory for the universal app. |
| platformProjectRoot | string | Project root for the specific platform. |
| modName | string | Name of the mod. |
| platform | ModPlatform | Name of the platform used in the mods config. |
| projectName | string | (iOS only) The path component used for querying project files. For example, projectRoot/ios/[projectName]/. |
For example, if you want to write a mod to update the Xcode Project's "product name", you'll create a config plugin file that uses the withXcodeProject
mod plugin.
my-config-plugin.ts
Copy
import { ConfigPlugin, withXcodeProject, IOSConfig } from 'expo/config-plugins'; const withCustomProductName: ConfigPlugin<string> = (config, customName) => { return withXcodeProject( config, async ( config ) => { config.modResults = IOSConfig.Name.setProductName({ name: customName }, config.modResults); return config; } ); }; // Usage: /// Create a config const config = { name: 'my app', }; /// Use the plugin export default withCustomProductName(config, 'new_name');
Show More
When implementing plugins, there are two fundamental approaches to consider:
Plugins defined within your app's project: These plugins live locally within your project, making them easy to customize and maintain alongside your app's code. They are ideal for project-specific customizations.
Standalone package plugins: These plugins exist as separate packages and are published to npm. This approach is ideal for reusable plugins that can be shared across multiple projects.
Both approaches provide the same capabilities for modifying your native configuration, but differ in how they're structured and imported. The sections below explain how module resolution works for each approach.
Any resolution pattern that isn't specified below is unexpected behavior, and subject to breaking changes.
With plugins defined within your app's project, you can implement plugins directly in your project in several ways:
You can quickly create a plugin in your project by creating a JavaScript/TypeScript file and use it in your config like any other JS/TS file.
app.config.ts``` import "./my-config-plugin"` ``
my-config-plugin.ts Imported from config
In the above example, the config plugin file contains a bare minimum function:
my-config-plugin.ts
Copy
module.exports = ({ config }: { config: ExpoConfig }) => {};
Expo config objects also support passing functions as-is to the plugins array. This is useful for testing, or if you want to use a plugin without creating a file.
app.config.ts
Copy
const withCustom = (config, props) => config; const config = { plugins: [ [ withCustom, { /* props */ }, ], withCustom, ], };
One caveat to using functions instead of strings is that serialization will replace the function with the function's name. This keeps manifests (kind of like the index.html for your app) working as expected. Here is what the serialized config would look like:
{ "plugins": [["withCustom", {}], "withCustom"] }
See Create a module with a config plugin for a step-by-step guide on how to create a standalone package plugin.
Standalone package plugins can be implemented in two ways:
These are npm packages whose sole purpose is to provide a config plugin. For a dedicated config plugin package, you can export your plugin using app.plugin.js:
app.config.ts``` import "expo-splash-screen"` ``
node_modules
āexpo-splash-screen``Node module
āāapp.plugin.js Entry file for custom plugins
āābuild
āāāindex.js Skipped in favor of `app.plugin.js`
When a config plugin is part of a Node module without an app.plugin.js, it uses the package's main entry point:
app.config.ts``` import "expo-splash-screen"` ``
node_modules
āexpo-splash-screen``Node module
āāpackage.json``` "main": "./build/index.js"` ``
āābuild
āāāindex.js Node resolve to this file
When you import a plugin package, files are resolved in this specific order:
app.config.ts``` import "expo-splash-screen"` ``
node_modules
āexpo-splash-screen``Node module
āāpackage.json``` "main": "./build/index.js"` ``
āāapp.plugin.js Entry file for custom plugins
āābuild
āāāindex.js Skipped in favor of app.plugin.js
app.config.ts``` import "expo-splash-screen"` ``
node_modules
āexpo-splash-screen``Node module
āāpackage.json``` "main": "./build/index.js"` ``
āābuild
āāāindex.js Node resolve to this file
Avoid importing module internals directly as it bypasses the standard resolution order and may break in future updates.
app.config.ts``` import "expo-splash-screen/build/index.js"` ``
node_modules
āexpo-splash-screen
āāpackage.json``` "main": "./build/index.js"` ``
āāapp.plugin.js Ignored due to direct import
āābuild
āāāindex.js `expo-splash-screen/build/index.js`
The app.plugin.js approach is preferred for config plugins as it allows different transpilation settings from the main package code. This is particularly important because Node environments often require different transpilation presets compared to Android, iOS, or web JS environments (for example, module.exports instead of import/export).