Config plugin structure and entry point
A config plugin is referenced in the `plugins` property of the app config file and is made up of one or more plugin functions. The top-level config plugin is the entry point and is conventionally named using the pattern `with<Plugin Name>`, for example `withMyPlugin`.
Plugin function definition
Plugin functions are one or more functions inside a config plugin that wrap the underlying logic of performing platform-specific modifications. Technically, plugin functions look just like the function for the top-level plugin itself and could be used as a plugin independently. Breaking plugins into smaller functions is helpful for testing and debugging.
Mod plugin function and mods
Mod plugin functions are wrapper functions from the `expo/config-plugins` library that provide a safe way to modify native files using mods. Developers use these functions instead of underlying mods. Mods are the underlying platform-specific modifiers like `mods.android.manifest` and `mods.ios.infoplist` that directly modify native project files during prebuild.
Config plugin uses and benefits
Config plugins can add native configuration not included by default. They can generate app icons, set the app name, configure AndroidManifest.xml and Info.plist, and perform other native modifications. In CNG projects, config plugins allow modification of native projects in a predictable way by consolidating changes into a configuration file and applying them when running `npx expo prebuild`. This avoids the need to manually update native files.
Config plugin synchronous behavior
Plugins are synchronous functions that accept an ExpoConfig and return a modified ExpoConfig. In rare cases, plugins can also be asynchronous if available methods to communicate with native projects are asynchronous, but they will not be performant. Plugins are always evaluated during the app config evaluation phase.
Config plugin naming convention
Plugins should be named using the convention `with<Plugin Functionality>`. For example, `withFacebook` or `withMyPlugin`.
Why avoid manual native project modifications in CNG
In Continuous Native Generation projects, it is best to avoid modifying native projects manually because you cannot regenerate them safely without potentially overwriting manual modifications. Config plugins provide the recommended way to consolidate native project changes into a configuration file.
Dynamic app config required for function-based plugins with parameters
Using a dynamic app config (app.config.js or app.config.ts instead of app.json) is required when you want to create or use a function-based config plugin that accepts parameters. Dynamic app config is not required for simple config plugins.
Config plugin hierarchy: creating and using plugins
Config plugins follow a hierarchy: creating a config plugin, using parameters with a config plugin, and chaining multiple config plugins together. The guide focuses on learning the first two parts of this hierarchy.
ConfigPlugin and withAndroidManifest import source
ConfigPlugin and withAndroidManifest are imported from 'expo/config-plugins'. withAndroidManifest is an async mod plugin that accepts a config and a data object and modifies the value before returning an object.
ConfigPlugin and withInfoPlist import source
ConfigPlugin and withInfoPlist are imported from 'expo/config-plugins'. withInfoPlist is an async mod plugin that accepts a config and a data object and modifies the value before returning an object.
Add meta-data to AndroidManifest using config plugin
To add meta-data to the AndroidManifest.xml file, access mainApplication['meta-data'] array, ensure it exists by initializing if necessary, then push an object with a $ property containing the android:name and android:value attributes.
Add custom key to Info.plist using config plugin
To add a custom key to Info.plist, assign the value directly to config.modResults with the key name, for example: config.modResults.HelloWorldMessage = message.
TypeScript support in config plugins requires tsx parser
To use TypeScript in config plugins, install the 'tsx' library as a dev dependency using npm/yarn/pnpm/bun, then add 'import "tsx/cjs";' at the top of app.config.ts. This allows Node.js to parse TypeScript files from the plugins directory.
Config plugin parameter passing pattern
To allow a config plugin to accept parameters, define a type for the options object (e.g., type AndroidProps = { message?: string }), declare the config plugin as ConfigPlugin<AndroidProps>, add an options parameter with a default empty object, and use options.propertyName with a fallback default value.
Pass parameters to config plugin in app.config.ts
To pass parameters to a config plugin in app.config.ts, use array syntax: pass the plugin path as the first element and an object with parameters as the second element, for example: ["./plugins/withPlugin.ts", { message: "Custom message" }].
Chaining config plugins execution order
Config plugins can be chained together by passing an array of plugins to the plugins array property in app.config.ts. Each plugin runs in the order it appears, with the output of one plugin becoming the input for the next. This is supported in both TypeScript and JSON app config formats.
withPlugins method for readable plugin chaining
The withPlugins method from 'expo/config-plugins' can be used to chain plugins together in a readable way instead of nested function calls. It takes a config object and an array of plugin/options pairs, executes them in order, and is especially useful when the plugins array is long or has complex configuration.
withPlugins syntax for plugins with and without parameters
When using withPlugins, pass plugins as [pluginFunction, parameter] for plugins with parameters, or just pluginFunction for plugins without parameters. Example: withPlugins(config, [[withFoo, 'input 1'], [withBar, 'input 2'], withDelta])
Installing Expo config plugins from npm
Expo config plugins are usually included in Node.js modules and can be installed like other libraries using npm, yarn, pnpm, or bun. For example, expo-camera has a config plugin that adds camera permissions to AndroidManifest.xml and Info.plist.
Using config plugin in app.json
To use a config plugin from an Expo library in app.json, add the plugin name to the plugins array: {"expo": {"plugins": ["expo-camera"]}}. For plugins that accept options, use array syntax: [["expo-camera", {"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."}]]
Prebuild compiles mods and modifies native files
Running 'npx expo prebuild' compiles the mods and changes native files. Changes don't take effect until you rebuild the native project. For CNG (Continuous Native Generation) projects without native directories, config plugins are applied during the prebuild step in EAS Build or when running 'npx expo prebuild|android|ios' locally.
Expo library config plugin documentation location
For every Expo library that has a config plugin, information about the plugin can be found in the library's API reference documentation. For example, expo-camera library has a config plugin section in its SDK documentation.
Verify config plugin modifications with prebuild
To see custom config applied in native projects, run 'npx expo prebuild --clean --no-install'. Then verify the modifications by opening android/app/src/main/AndroidManifest.xml and ios/<your-project-name>/Info.plist files to confirm the changes were applied correctly.
Plugin resolution order for packages
When you import a plugin package, files are resolved in this specific order: (1) app.plugin.js in package root (highest priority), (2) Package's main entry from package.json, (3) Direct internal imports (not recommended and bypasses standard resolution).
Avoid direct internal imports in plugins
Avoid importing module internals directly as it bypasses the standard resolution order and may break in future updates.
app.plugin.js transpilation benefits
The app.plugin.js approach is preferred for config plugins because it allows different transpilation settings from the main package code. Node environments often require different transpilation presets compared to Android, iOS, or web JS environments (for example, `module.exports` instead of `import/export`).
iOS mod plugins available
The following mod plugins are available for iOS in the expo/config-plugins library:
| Default iOS mod | Mod plugin | Dangerous | Description |
|---|---|---|---|
| `mods.ios.infoPlist` | `withInfoPlist` | No | Modify ios/<name>/Info.plist as JSON (parsed with @expo/plist) |
| `mods.ios.entitlements` | `withEntitlementsPlist` | No | Modify ios/<name>/<product-name>.entitlements as JSON (parsed with @expo/plist) |
| `mods.ios.expoPlist` | `withExpoPlist` | No | Modify ios/<name>/Expo.plist as JSON (Expo updates config for iOS) (parsed with @expo/plist) |
| `mods.ios.xcodeproj` | `withXcodeProject` | No | Modify ios/<name>.xcodeproj as an XcodeProject object (parsed with xcode) |
| `mods.ios.podfile` | `withPodfile` | No | Modify ios/Podfile as a string |
| `mods.ios.podfileProperties` | `withPodfileProperties` | No | Modify ios/Podfile.properties.json as JSON |
| `mods.ios.appDelegate` | `withAppDelegate` | Yes | Modify ios/<name>/AppDelegate.m as a string |
Dangerous mods use regex and should be avoided
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 should be used sparingly. Always opt toward using application code to modify application code (for example, Expo Modules native API) instead of dangerous mods.
Mods definition and purpose
Mods (short for modifiers) are asynchronous functions used by config plugins to modify native project files during the prebuild process. They allow you to make changes to platform-specific files such as AndroidManifest.xml and Info.plist without manually editing them. Mods execute only during the syncing phase of `npx expo prebuild`. They accept a config and a data object, then modify and return both as a single object.
Mods cannot be accessed via Updates.manifest
Mods are omitted from the manifest and cannot be accessed via Updates.manifest. Mods exist solely for the purpose of modifying native project files during code generation.
Mods object structure in app config
Mods are platform-specific and should always be added to a platform-specific object. The mods object is different from the rest of the app config because it does not get serialized, which means you can use it to perform actions during code generation.
modResults property in mod plugins
The modResults object contains the data to modify and return when a mod plugin is executed. Its type depends on the mod that is being used.
modRequest property structure
The modRequest object contains the following 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]/` |
Example mod plugin implementation
```ts my-config-plugin.ts
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:
const config = {
name: 'my app',
};
export default withCustomProductName(config, 'new_name');
```
This example shows how to create a config plugin that updates the Xcode Project's product name using the withXcodeProject mod plugin.
Mod plugins vs mods: when to use each
Mod plugins are wrappers around top-level mods that are made available from the expo/config-plugins library. When developing a feature that requires mods, you should use mod plugins instead of interacting with top-level mods directly. You do not use mods as top-level functions (for example `with.android.manifest`) directly in your config plugin.
Android mod plugins available
The following mod plugins are available for Android in the expo/config-plugins library:
| Default Android mod | Mod plugin | Dangerous | Description |
|---|---|---|---|
| `mods.android.manifest` | `withAndroidManifest` | No | Modify android/app/src/main/AndroidManifest.xml as JSON (parsed with xml2js) |
| `mods.android.strings` | `withStringsXml` | No | Modify android/app/src/main/res/values/strings.xml as JSON (parsed with xml2js) |
| `mods.android.colors` | `withAndroidColors` | No | Modify android/app/src/main/res/values/colors.xml as JSON (parsed with xml2js) |
| `mods.android.colorsNight` | `withAndroidColorsNight` | No | Modify android/app/src/main/res/values-night/colors.xml as JSON (parsed with xml2js) |
| `mods.android.styles` | `withAndroidStyles` | No | Modify android/app/src/main/res/values/styles.xml as JSON (parsed with xml2js) |
| `mods.android.gradleProperties` | `withGradleProperties` | No | Modify android/gradle.properties as a Properties.PropertiesItem[] |
| `mods.android.mainActivity` | `withMainActivity` | Yes | Modify android/app/src/main/<package>/MainActivity.java as a string |
| `mods.android.mainApplication` | `withMainApplication` | Yes | Modify android/app/src/main/<package>/MainApplication.java as a string |
| `mods.android.appBuildGradle` | `withAppBuildGradle` | Yes | Modify android/app/build.gradle as a string |
| `mods.android.projectBuildGradle` | `withProjectBuildGradle` | Yes | Modify android/build.gradle as a string |
| `mods.android.settingsGradle` | `withSettingsGradle` | Yes | Modify android/settings.gradle as a string |
Plugin module resolution: local vs standalone
There are two fundamental approaches to implementing plugins: (1) Plugins defined within your app's project live locally and are easy to customize and maintain alongside your app code, ideal for project-specific customizations. (2) Standalone package plugins exist as separate packages published to npm, ideal for reusable plugins shared across multiple projects. Both provide the same capabilities for modifying native configuration but differ in structure and import.
Local plugin file import
You can quickly create a plugin in your project by creating a JavaScript/TypeScript file and using it in your config like any other JS/TS file. The plugin file should contain a bare minimum function: `module.exports = ({ config }: { config: ExpoConfig }) => {};`
Inline function plugins in app config
Expo config objects support passing functions directly to the plugins array. This is useful for testing or using a plugin without creating a file. Functions passed this way will be serialized with their function name in the manifest.
Standalone package plugin entry point: app.plugin.js
For standalone npm packages whose sole purpose is to provide a config plugin, you can export your plugin using app.plugin.js. This file is the entry point for custom plugins in dedicated config plugin packages.
Standalone package plugin with main entry point
When a config plugin is part of a Node module without an app.plugin.js file, the package's main entry point from package.json is used to load the config plugin.
expo-font config plugin limitations
The expo-font config plugin does not work with Expo Go because it requires creating a development build.
Font family naming on iOS with config plugin
On iOS, the expo-font config plugin always extracts the font family name from the font file itself, regardless of the file name or configuration method used.
Google Fonts via config plugin file path
When embedding Google Fonts using the expo-font config plugin, the path to the font file is defined relative to the node_modules directory. For example, for @expo-google-fonts/inter, use path like node_modules/@expo-google-fonts/inter/900Black/Inter_900Black.ttf.
Google Fonts via config plugin platform-specific naming
When using Google Fonts with the expo-font config plugin, on Android use the font file name (e.g., 'Inter_900Black'), and on iOS use the font and its weight name (PostScript name, e.g., 'Inter-Black'). Use Platform.select() to choose the correct font family name for each platform.
expo-font config plugin object syntax properties
When using object syntax with the expo-font config plugin on Android, you can specify: fontFamily (required), weight (optional), and style (optional, defaults to 'normal'). These properties embed fonts as native XML resources on Android.
expo-font config plugin supported formats
The expo-font config plugin supports ttf and otf for both Android and iOS. On iOS only, woff and woff2 are also supported.
expo-font config plugin benefits
Using the expo-font config plugin for embedding fonts provides these benefits: fonts are available immediately when the app starts on a device, no additional code required to load fonts asynchronously at app startup, and fonts are consistently available across all devices where the app is installed because they're bundled within the app.
Font family naming on Android with config plugin
When using the expo-font config plugin on Android, if you provide font file paths as an array without object syntax, the file name (without extension) becomes the font family name. For example, a file named FiraSans-MediumItalic.ttf becomes fontFamily 'FiraSans-MediumItalic'. When using object syntax with fontDefinitions, you provide the fontFamily explicitly.
Basic app icon configuration in app.json
Add the icon property to the app config with the local path to your app icon image:
```json
{
"icon": "./assets/images/icon.png"
}
```
Basic splash screen configuration in app.json
Configure the splash screen in app.json under the plugins section. The expo-splash-screen plugin accepts the following properties: backgroundColor (string, required), image (string, path to splash icon), imageWidth (number), and dark (object with image and backgroundColor for dark mode support).
Splash screen configuration example
Example splash screen configuration in app.json:
```json
{
"expo": {
"plugins": [
[
"expo-splash-screen",
{
"backgroundColor": "#232323",
"image": "./assets/images/splash-icon.png",
"dark": {
"image": "./assets/images/splash-icon-dark.png",
"backgroundColor": "#000000"
},
"imageWidth": 200
}
]
]
}
}
```
This shows backgroundColor, image path, dark mode overrides, and imageWidth configuration.
Platform-specific splash screen configuration
The expo-splash-screen plugin supports android and ios properties for platform-specific configuration. You can provide different backgroundColor, image, imageWidth, and resizeMode values for each platform.
Android adaptive icon configuration
Use the android.adaptiveIcon property to configure adaptive icons for Android. Adaptive icons consist of a foreground image and background color or image. Provide the foreground image using android.adaptiveIcon.foregroundImage, optionally a monochrome image using android.adaptiveIcon.monochromeImage, and set the background with android.adaptiveIcon.backgroundColor (default is white) or android.adaptiveIcon.backgroundImage. For older Android devices without adaptive icon support, use android.icon to provide a combined single icon.
iOS app icon configuration with appearance variants
For iOS, use ios.icon to specify different icons for various system appearances. You can provide dark, light, and tinted variants:
```json
{
"expo": {
"ios": {
"icon": {
"dark": "./assets/images/ios-dark.png",
"light": "./assets/images/ios-light.png",
"tinted": "./assets/images/ios-tinted.png"
}
}
}
}
```
When ios.icon is specified, it overrides the top-level icon key.
iOS Icon Composer .icon directory support
iOS app icons using the Icon Composer .icon directory format are supported in SDK 54 and later. You can add the output .icon directory to your project's assets directory and reference it via ios.icon property. This approach handles dark mode support automatically.
Config plugins for Expo Prebuild
Authors can configure their libraries to work with Expo Prebuild by creating a config plugin. This means you can use any library with Expo Prebuild. You can also use any custom native code with Expo Prebuild by creating a development build.
Adding custom native code to Expo projects
Expo supports adding custom native code and customizing native code (Android/Xcode projects). To use custom native code, you can create a development build and use config plugins. The Expo SDK modules are recommended when possible for easier upgrades and improved developer experience.
Migrate native customizations to app config
If the project has native modifications in the android or ios directories, configure the app config (app.json) to reflect those changes. Check if changes overlap with built-in app config fields, look up if packages require Expo config plugins, use the VS Code Expo extension to introspect changes, or develop local config plugins.