# Config plugins

## Introduction to config plugins

An introduction to Expo config plugins.

When using [Continuous Native Generation (CNG)](/workflow/continuous-native-generation/) in a project, native project (**android** and **ios** directories) changes are implemented without directly interacting with the native project files. Instead, you can use a config plugin to automatically configure your native project beyond what can be configured using the default app config props.
## What is a config plugin
A config plugin is a top-level custom configuration point that is not built into the [app config](/workflow/configuration). Using a config plugin, you can modify native projects created during the [prebuild](/workflow/continuous-native-generation/#usage) process in CNG projects.
A config plugin is referenced in the `plugins` property of the [app config](/workflow/configuration) file and is made up of one or more plugin functions. These plugin functions are written in JavaScript and are executed during the prebuild process.
## Glossary
A typical config plugin is made up of one or more plugin functions that work together. The following diagram shows how the different parts of a config plugin interact with each other:
<ConfigPluginHierarchy highlightedNodeId="1" />
In the following guides, we will use the above diagram to highlight specific terminology explained below:
### Plugin
The top-level config plugin which is referenced in your app config's `plugins` array. This is the entry point for your plugin. Conventionally, it is named `with<Plugin Name>`. For example, `withMyPlugin`. It is made of one or more [plugin functions](#plugin-function).
### Plugin function
One or more functions inside a config plugin that are called _plugin functions_. They 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 often helpful for testing and debugging.
### Mod plugin function
Wrapper functions from `expo/config-plugins` library that provide a safe way to modify native files using `mods`. As a developer, you will use these functions in your config plugin instead of underlying `mods`.
### Mod
The underlying platform-specific modifiers (like `mods.android.manifest` and `mods.ios.infoplist`) that directly modify native project files during prebuild.
## Why use a config plugin
Config plugins can add native configuration to your project that isn't included by default. They can be used to generate app icons, set the app name, configure **AndroidManifest.xml** and **Info.plist**, and so on.
In CNG projects, it is best to avoid modifying these native projects manually, because you cannot regenerate them safely without potentially overwriting manual modifications. Config plugins allow you to modify these native projects in a _predictable way_ by consolidating your native project changes into a configuration file and applying them when you run `npx expo prebuild` (either manually or automatically during a CI/CD process). For example, when you change the name of your app in app config and run `npx expo prebuild`, the name will change in your native projects automatically without the need to manually update **AndroidManifest.xml** and **Info.plist** files.
## Characteristics of a config plugin
Config plugins have the following characteristics:
- Plugins are **synchronous** functions that accept an [ExpoConfig](/workflow/configuration) 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 won't be performant.
- Plugins should be named using the following convention: `with<Plugin Functionality>`, for example, `withFacebook`
- Plugins should be synchronous and their return value should be serializable, except for adding any [`mods`](#mods)
- Plugins are always evaluated during the app config evaluation phase.
- Optionally, a second argument can be passed to the plugin to configure it
- Mods are only evaluated during the **syncing** phase of `npx expo prebuild` (prebuild process) and modify native files during code generation. Because of that, any modifications made to app config in a config plugin should be outside of a mod to ensure that they are executed in non-prebuild configuration scenarios.
## Get started


## Create and use config plugins

Learn how to create and use a config plugins in your Expo project.

This guide covers sections on how to create a config plugin, how to pass parameters to a config plugin, and how to chain multiple config plugins together. It also covers how to use a config plugin from an Expo library.
Using the diagram below, in this guide, you will learn the first two parts of the config plugin hierarchy:
<ConfigPluginHierarchy highlightedNodeIds={['1', '2']} />
> **info** **Note:** The following sections use dynamic [app config](/workflow/configuration/) (**app.config.js/app.config.ts** instead of **app.json**), which is not required to use a simple config plugin. However, it is required to use dynamic app config when you want to create/use a function-based config plugin that accepts parameters.
## Creating a config plugin
In the following section, let's create a local config plugin that adds an arbitrary property `HelloWorldMessage` to the **AndroidManifest.xml** for Android and **Info.plist** for iOS.
This example will create and modify the following files. To follow along, create a **plugins** directory in the root of your project, and inside it, create **withAndroidPlugin.ts**, **withIosPlugins.ts**, and **withPlugin.ts** files.
```
âââ plugins/
â   âââ withAndroidPlugin.ts  # Contains Android-specific modifications
â   âââ withIosPlugin.ts  # Contains iOS-specific modifications
â   âââ withPlugin.ts  # Main plugin file that combines both Android and iOS plugins
âââ app.config.ts  # Dynamic app config file that uses the plugin
```
Step 1: 
### Create Android plugin
In **withAndroidPlugin.ts**, add the following code:
```ts withAndroidPlugin.ts
import { ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
const withAndroidPlugin: ConfigPlugin = config => {
  // Define a custom message
  const message = 'Hello world, from Expo plugin!';
  return withAndroidManifest(config, config => {
    const mainApplication = config?.modResults?.manifest?.application?.[0];
    if (mainApplication) {
      // Ensure meta-data array exists
      if (!mainApplication['meta-data']) {
        mainApplication['meta-data'] = [];
      }
      // Add the custom message as a meta-data entry
      mainApplication['meta-data'].push({
        $: {
          'android:name': 'HelloWorldMessage',
          'android:value': message,
        },
      });
    }
    return config;
  });
};
export default withAndroidPlugin;
```
The example code above adds a meta-data entry `HelloWorldMessage` to the **android/app/src/main/AndroidManifest.xml** file by importing `ConfigPlugin` and `withAndroidManifest` from the `expo/config-plugins` library. The [`withAndroidManifest`](/config-plugins/mods/#mod-plugins) mod plugin is an asynchronous function that accepts a config and a data object and modifies the value before returning an object.
Step 2: 
### Create iOS plugin
In **withIosPlugin.ts**, add the following code:
```ts withIosPlugin.ts
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins';
const withIosPlugin: ConfigPlugin = config => {
  // Define the custom message
  const message = 'Hello world, from Expo plugin!';
  return withInfoPlist(config, config => {
    // Add the custom message to the Info.plist file
    config.modResults.HelloWorldMessage = message;
    return config;
  });
};
export default withIosPlugin;
```
The example code above adds `HelloWorldMessage` as the custom key with a custom message in **ios/\<your-project-name\>/Info.plist** file by importing the `ConfigPlugin` and `withInfoPlist` from the `expo/config-plugins` library. The [`withInfoPlist`](/config-plugins/mods/#mod-plugins) mod plugin is an asynchronous function that accepts a config and a data object and modifies the value before returning an object.
Step 3: 
### Create a combined plugin
Now you can create a combined plugin that applies both platform-specific plugins. This approach allows the maintenance of platform-specific code separately while providing a single entry point.
In **withPlugin.ts**, add the following code:
```ts withPlugin.ts
import { ConfigPlugin } from 'expo/config-plugins';
import withAndroidPlugin from './withAndroidPlugin';
import withIosPlugin from './withIosPlugin';
const withPlugin: ConfigPlugin = config => {
  // Apply Android modifications first
  config = withAndroidPlugin(config);
  // Then apply iOS modifications and return
  return withIosPlugin(config);
};
export default withPlugin;
```
Step 4: 
### Add TypeScript support and convert to dynamic app config
We recommend writing config plugins in TypeScript, since this will provide intellisense for the configuration objects. However, your app config is ultimately evaluated by Node.js, which does not recognize TypeScript code by default. Therefore, you will need to add a parser for the TypeScript files from the **plugins** directory to **app.config.ts** file.
Install `tsx` library by running the following command:
```sh
$ npm install --save-dev tsx
```
Then, change the static app config (**app.json**) to the [dynamic app config (**app.config.ts**)](/workflow/configuration/#dynamic-configuration) file. You can do this by renaming the **app.json** file to **app.config.ts** and changing the content of the file as shown below. Ensure to add the following import statement at the top of your **app.config.ts** file:
```ts app.config.ts
import 'tsx/cjs';
module.exports = () => {
};
```
Step 5: 
### Call the config plugin from your dynamic app config
Now, you can call the config plugin from your dynamic app config. To do this, you need to add the path to the **withPlugin.ts** file to the plugins array in your app config:
```ts app.config.ts
import "tsx/cjs";
import { ExpoConfig } from "expo/config";
  plugins: [
      ["./plugins/withPlugin.ts"],
    ],
};
```
To see the custom config applied in native projects, run the following command:
```sh
$ npx expo prebuild --clean --no-install
```
To verify the custom config plugins applied, open **android/app/src/main/AndroidManifest.xml** and **ios/\<your-project-name\>/Info.plist** files:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- ... rest of the configuration-->
	<application ...>
		<meta-data android:name="HelloWorldMessage" android:value="Hello world, from Expo plugin!"/>
		<!-- ... -->
	</application>
</manifest>
```
```xml
<plist version="1.0">
  <dict>
  <!-- ... -->
    <key>HelloWorldMessage</key>
    <string>Hello world, from Expo plugin!</string>
	<!-- ... -->
	</dict>
</plist>
```
## Passing a parameter to a config plugin
Your config plugin can accept parameters passed from your app config. To do so, you will need to read the parameter in your config plugin function, and then pass an object containing the parameter along with the config plugin function in your app config.
Step 1: 
Considering the previous example, let's pass a custom message to the plugin. Add an `options` object in **withAndroidPlugin.ts** and update the `message` variable to use the `options.message` property:
```ts withAndroidPlugin.ts
type AndroidProps = {
  message?: string;
};
const withAndroidPlugin: ConfigPlugin<AndroidProps> = (
  config,
) => {
  const message = options.message || 'Hello world, from Expo plugin!';
  return withAndroidManifest(config, config => {
  });
};
export default withAndroidPlugin;
```
Step 2: 
Similarly, add an `options` object in **withIosPlugin.ts** and update the `message` variable to use the `options.message` property:
```ts withIosPlugin.ts
type IosProps = {
  message?: string;
};
  const message = options.message || 'Hello world, from Expo plugin!';
};
export default withIosPlugin;
```
Step 3: 
Update the **withPlugin.ts** file to pass the `options` object to both plugins:
```ts withPlugin.ts
  config = withAndroidPlugin(config, options);
  return withIosPlugin(config, options);
};
```
Step 4: 
To pass a value dynamically to the plugin, you can pass an object with the `message` property to the plugin in your app config:
```ts app.config.ts
{
  plugins: [
    [
      "./plugins/withPlugin.ts",
      { message: "Custom message from app.config.ts" },
    ],
  ],
}
```
## Chaining config plugins
Config plugins can be chained together to apply multiple modifications. Each plugin in the chain runs in the order it appears, with the output of one plugin becoming the input for the next. This sequential execution ensures that dependencies between plugins are respected and allows you to control the precise order of modifications to your native code.
To chain config plugins, you can pass an array of plugins to the `plugins` array property in your app config. This is also supported in JSON app config file format (**app.json**).
```ts app.config.ts
module.exports = ({ config }: { config: ExpoConfig }) => {
  name: 'my app',
  plugins: [
    [withFoo, 'input 1'],
    [withBar, 'input 2'],
    [withDelta, 'input 3'],
  ],
};
```
The `plugins` array uses `withPlugins` method under the hood to chain the plugins. If your plugins array is getting long or has complex configuration, you can use the `withPlugins` method directly to make your configuration easier to read. `withPlugins` will chain the plugins together and execute them in order.
```ts app.config.ts
import { withPlugins } from 'expo/config-plugins';
// Create a base config object
const baseConfig = {
  name: 'my app',
};
// â Hard to read
withDelta(withFoo(withBar(config, 'input 1'), 'input 2'), 'input 3');
// â Easy to read
withPlugins(config, [
  [withFoo, 'input 1'],
  [withBar, 'input 2'],
  // When no input is required, you can just pass the method
  withDelta,
]);
// Export the base config with plugins applied
module.exports = ({ config }: { config: ExpoConfig }) => {
  return withPlugins(baseConfig, plugins);
};
```
## Using a config plugin
Expo config plugins are usually included in Node.js modules. You can install them just like other libraries in your project.
For example, `expo-camera` has a plugin that adds camera permissions to the **AndroidManifest.xml** and **Info.plist**. To install it in your project, run the following command:
```sh
$ npx expo install expo-camera
```
In your [app config](/versions/latest/config/app/), you can add `expo-camera` to the list of plugins:
```json app.json
{
  "expo": {
    "plugins": ["expo-camera"]
  }
}
```
Some config plugins offer flexibility by allowing you to pass options to customize their configuration. To do this, you can pass an array with the Expo library name as the first argument, and an object containing the options as the second argument. For example, the `expo-camera` plugin allows you to customize the camera permission message:
```json app.json
{
  "expo": {
    "plugins": [
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."
        }
      ]
    ]
  }
}
```
> **info** **Tip**: For every Expo library that has a config plugin, you'll find more information about that in the library's API reference. For example, the [`expo-camera` library has a config plugin section](/versions/latest/sdk/camera/#configuration-in-appjsonappconfigjs).
On running the `npx expo prebuild`, the [`mods`](/config-plugins/introduction/#mods) are compiled, and the native files change.
The changes don't take effect until you rebuild the native project, for example, with Xcode. **If you're using config plugins in a project without native directories (CNG projects), they will be applied during the prebuild step in EAS Build** or when running `npx expo prebuild|android|ios` locally.


## Mods

Learn about mods and how to use them when creating a config plugin.

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:
<ConfigPluginHierarchy highlightedNodeIds={['3', '4']} />
## Mod plugins
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](#mods)_) because top-level mods are platform-specific and perform various tasks that can be difficult to understand at first.
> **info** **Tip:** If you are developing a feature that requires mods, you should use _mod plugins_ instead of interacting with top-level mods directly.
### Available mod plugins
The following mod plugins are available in the `expo/config-plugins` library:
#### Android
| Default Android mod               | Mod plugin                                                                                                                                                                             |    Dangerous    | Description                                                                                               |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------: | --------------------------------------------------------------------------------------------------------- |
| `mods.android.manifest`           | `withAndroidManifest` ([Example](https://github.com/expo/expo/blob/main/packages/expo-notifications/plugin/src/withNotificationsAndroid.ts))                                           |        -        | Modify the **android/app/src/main/AndroidManifest.xml** as JSON (parsed with [`xml2js`][xml2js])          |
| `mods.android.strings`            | `withStringsXml` ([Example](https://github.com/expo/expo/blob/d7fb5d254d5cb57ab06055136db72b9347d3db1e/packages/expo-navigation-bar/plugin/src/withNavigationBar.ts))                  |        -        | Modify the **android/app/src/main/res/values/strings.xml** as JSON (parsed with [`xml2js`][xml2js]).      |
| `mods.android.colors`             | `withAndroidColors` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/StatusBar.ts#L8))                                                    |        -        | Modify the **android/app/src/main/res/values/colors.xml** as JSON (parsed with [`xml2js`][xml2js]).       |
| `mods.android.colorsNight`        | `withAndroidColorsNight` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/unversioned/expo-splash-screen/withAndroidSplashStyles.ts#L5)) |        -        | Modify the **android/app/src/main/res/values-night/colors.xml** as JSON (parsed with [`xml2js`][xml2js]). |
| `mods.android.styles`             | `withAndroidStyles` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/unversioned/expo-splash-screen/withAndroidSplashStyles.ts#L5))      |        -        | Modify the **android/app/src/main/res/values/styles.xml** as JSON (parsed with [`xml2js`][xml2js]).       |
| `mods.android.gradleProperties`   | `withGradleProperties` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/BuildProperties.ts#L5))                                           |        -        | Modify the **android/gradle.properties** as a `Properties.PropertiesItem[]`.                              |
| `mods.android.mainActivity`       | `withMainActivity` ([Example](https://github.com/expo/expo/blob/main/packages/install-expo-modules/src/plugins/android/withAndroidModulesMainActivity.ts#L2))                          | <WarningIcon /> | Modify the **android/app/src/main/&lt;package&gt;/MainActivity.java** as a string.                        |
| `mods.android.mainApplication`    | `withMainApplication` ([Example](https://github.com/expo/expo/blob/main/packages/expo-web-browser/plugin/src/withWebBrowserAndroid.ts#L8))                                             | <WarningIcon /> | Modify the **android/app/src/main/&lt;package&gt;/MainApplication.java** as a string.                     |
| `mods.android.appBuildGradle`     | `withAppBuildGradle` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/GoogleServices.ts#L5))                                              | <WarningIcon /> | Modify the **android/app/build.gradle** as a string.                                                      |
| `mods.android.projectBuildGradle` | `withProjectBuildGradle` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/android/GoogleServices.ts#L5))                                          | <WarningIcon /> | Modify the **android/build.gradle** as a string.                                                          |
| `mods.android.settingsGradle`     | `withSettingsGradle` ([Example](https://github.com/expo/expo/blob/main/packages/install-expo-modules/src/plugins/android/withAndroidSettingsGradle.ts#L2))                             | <WarningIcon /> | Modify the **android/settings.gradle** as a string.                                                       |
#### iOS
| Default iOS mod              | Mod plugin                                                                                                                                    | Dangerous       | Description                                                                                                                         |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `mods.ios.infoPlist`         | `withInfoPlist` ([Example](https://github.com/expo/expo/blob/main/packages/expo-location/plugin/src/withLocation.ts))                         | -               | Modify the **ios/&lt;name&gt;/Info.plist** as JSON (parsed with [`@expo/plist`][expo-plist]).                                       |
| `mods.ios.entitlements`      | `withEntitlementsPlist` ([Example](https://github.com/expo/expo/blob/main/packages/expo-apple-authentication/plugin/src/withAppleAuthIOS.ts)) | -               | Modify the **ios/&lt;name&gt;/&lt;product-name&gt;.entitlements** as JSON (parsed with [`@expo/plist`][expo-plist]).                |
| `mods.ios.expoPlist`         | `withExpoPlist` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/Updates.ts#L6))                     | -               | Modify the **ios/&lt;name&gt;/Expo.plist** as JSON (Expo updates config for iOS) (parsed with [`@expo/plist`][expo-plist]).         |
| `mods.ios.xcodeproj`         | `withXcodeProject` ([Example](https://github.com/expo/expo/blob/main/packages/expo-asset/plugin/src/withAssetsIos.ts))                        | -               | Modify the **ios/&lt;name&gt;.xcodeproj** as an `XcodeProject` object (parsed with [`xcode`](https://www.npmjs.com/package/xcode)). |
| `mods.ios.podfile`           | `withPodfile` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/Maps.ts#L6)                           | -               | Modify the **ios/Podfile** as a string.                                                                                             |
| `mods.ios.podfileProperties` | `withPodfileProperties` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/BuildProperties.ts#L4))     | -               | Modify the **ios/Podfile.properties.json** as JSON.                                                                                 |
| `mods.ios.appDelegate`       | `withAppDelegate` ([Example](https://github.com/expo/expo/blob/main/packages/%40expo/config-plugins/src/ios/Maps.ts#L6))                      | <WarningIcon /> | Modify the **ios/&lt;name&gt;/AppDelegate.m** as a string.                                                                          |
> **info** **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 toward using application code to modify application code, that is, [Expo Modules](https://github.com/expo/expo/tree/main/packages/expo-modules-core) native API.
## Mods
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`](#available-mod-plugins).
Note: 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:
- The config is read using [`getPrebuildConfig`](https://github.com/expo/expo/blob/efc2db4eb1c909544e28792a15c89f8d22113c5b/packages/%40expo/prebuild-config/src/getPrebuildConfig.ts#L28) from `@expo/prebuild-config`
- All of the core functionality supported by Expo is added via plugins in `withIosExpoPlugins`. This includes name, version, icons, locales, and so on.
- The config is passed to the compiler `compileModsAsync`
- The compiler adds base mods that are responsible for reading data (like **Info.plist**), executing a named mod (like `mods.ios.infoPlist`), then writing the results to the file system
- The compiler iterates over all the mods and asynchronously evaluates them, providing some base props like the `projectRoot`
  - After each mod, error handling asserts if the mod chain was corrupted by an invalid mod
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:
  ```ts app.config.ts
  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.
---
### How mod plugins work
When a mod plugin is executed, it gets passed a `config` object with additional properties: `modResults` and `modRequest`.
#### `modResults`
The `modResults` object contains the data to modify and return. Its type depends on the mod that's being used.
#### `modRequest`
The `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]/`. |
## Create your own mod
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`](#ios) mod plugin.
```ts my-config-plugin.ts
import { ConfigPlugin, withXcodeProject, IOSConfig } from 'expo/config-plugins';
const withCustomProductName: ConfigPlugin<string> = (config, customName) => {
  return withXcodeProject(
    config,
    async (
    ) => {
      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');
```
## Plugin module resolution
When implementing plugins, there are two fundamental approaches to consider:
1. **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.
2. **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.
### Plugins defined within your app's project
With plugins defined within your app's project, you can implement plugins directly in your project in several ways:
#### File import
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:
```ts my-config-plugin.ts
module.exports = ({ config }: { config: ExpoConfig }) => {};
```
#### Inline function inside of dynamic app config
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.
```js app.config.ts
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:
```json
{
  "plugins": [["withCustom", {}], "withCustom"]
}
```
### Standalone package plugins
> **info** See [Create a module with a config plugin](/modules/config-plugin-and-native-module-tutorial/) for a step-by-step guide on how to create a standalone package plugin.
Standalone package plugins can be implemented in two ways:
#### 1. Dedicated config plugin packages
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"
```
#### 2. Config plugins with companion packages
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
```
### Plugin resolution order
When you import a plugin package, files are resolved in this specific order:
1. **app.plugin.js in package root**
```
âââ 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
```
2. **Package's main entry (from package.json)**
```
âââ 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
```
3. **Direct internal imports** (not recommended)
> **error** 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"
```
### Why use app.plugin.js for plugins
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`).
[xml2js]: https://www.npmjs.com/package/xml2js
[expo-plist]: https://www.npmjs.com/package/@expo/plist


## Using a dangerous mod

Learn about dangerous mods and how to use them when creating a config plugin.

Dangerous mods in Expo provide direct access to native project files through string manipulation and regular expressions. While [existing mod plugins](/config-plugins/mods) are the recommended approach, dangerous mods serve as an escape hatch for modifications that cannot be achieved through existing mod plugins.
Note: Why are they considered dangerous?
---
Automated direct source code manipulation does not typically compose well. For example, if a dangerous mod replaces text in a source file, and a subsequent dangerous mod expects the original text to be there (perhaps it uses the original text as an anchor for a regular expression) then it is unlikely produce the desired result â depending on how it is written, it may either throw an error or log. Other types of mods are less prone to this type of problem, although it can happen with mods that manipulate source files directly like `withAndroidManifest` and `withPodfile`.
Unlike standard mods, which can run multiple times safely, dangerous mods are rarely guaranteed to be idempotent. Running the same dangerous mod multiple times may produce different results, cause duplicate modifications, or break the target file entirely.
---
## When to use a dangerous mod
Consider using a dangerous mod when:
- **Can't make the modification with a standard mod**: The modification you need isn't supported by existing mod plugins like [`withAndroidManifest`](/config-plugins/mods/#android), [`withPodfile`](/config-plugins/mods/#ios), and so on, or if a library requires specific native modifications that aren't covered by standard plugins.
- **Legacy Expo SDK compatibility:** You are targeting an older Expo SDK version that doesn't include the mod plugin you need.
- **Need to modify text with regexes or replace functions**: You need to perform intricate text manipulations that existing mod plugins do not support. Expo uses dangerous mods internally for large file system refactoring, for example, when a library's name changes.
## How to use a dangerous mod
In a real-world scenario, you can use the example config plugin described in this section directly in your project by following the standard config plugin usage pattern from the [Creating a config plugin section](/config-plugins/plugins/#creating-a-config-plugin). However, with the existing mod plugin called [`withPodfile`](/config-plugins/mods/#ios), you don't have to use the dangerous mod. The example below is just for demonstration of how a dangerous mod can be created and used.
Let's take a look at an example config plugin to modify a file inside a native directory (**ios**). This is useful when you are using Continuous Native Generation in your Expo project. With the help of this config plugin, the native file (**ios/Podfile**) will update anytime the `npx expo prebuild` command runs, whether you run it manually or using EAS Build). This example is an ideal use case when an existing mod plugin cannot edit and update a file inside a native directory.
Following the directory structure and steps to create a config plugin (steps 3, 4, and 5) from [Creating a config plugin section](/config-plugins/plugins/#creating-a-config-plugin), let's assume this config plugin is created inside the **plugins** directory of your Expo project:
```tsx withCustomPodfile.ts
import { ConfigPlugin, IOSConfig, withDangerousMod } from 'expo/config-plugins';
import fs from 'fs/promises';
import path from 'path';
const withCustomPodfile: ConfigPlugin = config => {
  return withDangerousMod(config, [
    'ios',
    async config => {
      const podfilePath = path.join(config.modRequest.platformProjectRoot, 'Podfile');
      try {
        let contents = await fs.readFile(podfilePath, 'utf8');
        const projectName = IOSConfig.XcodeUtils.getProjectName(config.modRequest.projectRoot);
        contents = addCustomPod(contents, projectName);
        await fs.writeFile(podfilePath, contents);
        console.log('â Successfully added custom pod to Podfile');
      } catch (error) {
        console.warn('â ï¸ Podfile not found, skipping modification');
      }
      return config;
    },
  ]);
};
function addCustomPod(contents: string, projectName: string): string {
  if (contents.includes("pod 'Alamofire'")) {
    console.log('Alamofire pod already exists, skipping');
    return contents;
  }
  const targetRegex = new RegExp(
    `(target ['"]${projectName}['"] do[\\s\\S]*?use_expo_modules!)`,
    'm'
  );
  return contents.replace(targetRegex, `$1\n  pod 'Alamofire', '~> 5.6'`);
}
export default withCustomPodfile;
```
In the example above, the plugin **withCustomPodfile** will add a CocoaPod dependency automatically to your project's native **ios/Podfile** during the prebuild process. It uses `withDangerousMod` to provide access to the native file system directly and run after the native project is generated, but before any CocoaPod dependency is installed.
The **Podfile** requires direct text manipulation, which is done using a regex pattern inside `addCustomMod` function. This process also requires that the CocoaPod dependency is inserted into the **Podfile** at a specific location, which is after the `use_expo_modules!` statement.
## `withDangerousMod` syntax and requirements
Using `withDangerousMod` requires certain parameters:
1. A native platform (**android** or **ios**)
2. An asynchronous function that receives `config` object with file system access
3. Relative file name/path to access inside the native directory
4. Reading the existing file, modifying its contents, and writing back to the file
5. (Optional) Log custom messages for success and failure state when a plugin executes during the prebuild process
The code snippet below provides a skeleton of the required field and how the config plugin can be structured when using `withDangerousMod`:
```tsx
import { ConfigPlugin, withDangerousMod } from 'expo/config-plugins';
import fs from 'fs/promises';
import path from 'path';
const myPlugin: ConfigPlugin = config => {
  return withDangerousMod(config, [
    'platform', // 1. "ios" | "android"
    async config => {
      // 2. Async modification function
      // 3. Build file paths
      const filePath = path.join(
        config.modRequest.platformProjectRoot, // Native project root
        'path/to/file' // Relative path to target file
      );
      try {
        // 4. Read existing file, modify its contents, and write back to the file
        let contents = await fs.readFile(filePath, 'utf8');
        contents = modifyContents(contents);
        await fs.writeFile(filePath, contents);
        // 5. Log success and failure states
        console.log('â Successfully modified file');
      } catch (error) {
        console.warn('â ï¸ File modification failed:', error);
      }
      return config;
    },
  ]);
};
// Helper functions to use regex to modify the contents of the file
```
### Available paths in config plugins
Different path properties available in config plugins:
| Path                                          | Type      | Description                                                                                                                                                                                                                                            |
| --------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `config.modRequest.projectRoot`               | `string`  | Universal app project root directory where **package.json** is located. Used for resolving assets, reading **package.json**, and cross-platform operations. Always verify the directory exists and contains **package.json**.                          |
| `config.modRequest.platformProjectRoot`       | `string`  | Platform-specific project root (**projectRoot/android** or **projectRoot/ios**). Used for platform-specific file operations like modifying native configuration files. Ensure the platform directory exists relative to main `projectRoot`.            |
| `config.modRequest.projectName`               | `string`  | [iOS only] Project name component for constructing iOS file paths (for example, **projectRoot/ios/[projectName]/**). Used for iOS-specific file path construction. Only available on iOS platform and should match the actual Xcode project structure. |
| `config.modRequest.introspect`                | `boolean` | Whether running in introspection mode where no filesystem changes should be made. When `true`, mods should only read and analyze files without writing. Used during config analysis and validation.                                                    |
| `config.modRequest.ignoreExistingNativeFiles` | `boolean` | Whether to ignore existing native files. Used in template-based operations, particularly affects entitlements and other native configs to ensure alignment with prebuild expectations.                                                                 |
## Considerations when using a dangerous mod
When using a dangerous mod, consider the following:
- **Limited idempotency guarantees.** Unlike standard mods, which are generally idempotent and can work without the clean flag, dangerous mods are **rarely guaranteed to be idempotent**. This means running the same dangerous mod multiple times may produce different results or cause issues.
- **Experimental and prone to breakage.** Be careful using `withDangerousMod` as it is subject to change in the future. Test your dangerous mods thoroughly with each SDK release, as they are especially prone to breakage when native template changes occur.
- **Use standard mod plugins**. Both Android and iOS offer mod plugins like `withAndroidManifest`, `withPodfile`, `withPodfileProperties`, and so on, to perform common native file modifications. Only use a dangerous mod when there are no [existing mod plugins available](/config-plugins/mods/#available-mod-plugins) to handle your use case.
- **Don't assume a file exists**. Always check the native directory and the relative path to the file before reading/writing to it. If you use CNG, you can always run `npx expo prebuild` to create native **android** and **ios** directories and manually verify a file's existence.
- **Dangerous mods run first**. The order in which dangerous mods execute might be unreliable since dangerous mods run before other modifiers. This can affect the predictability of your build process and may cause conflicts with other modifications.


## Plugin development for libraries

Learn how to develop config plugins for Expo and React Native libraries.

Expo config plugins in a React Native library represent a transformative approach to automating native project configuration. Rather than requiring library users to manually edit native files, such as **AndroidManifest.xml**, **Info.plist**, and so on, you can provide a plugin that handles these configurations automatically during the prebuild process. This changes developer experience from error-prone manual setup to reliable, automated configuration that can work consistently across different projects.
This guide explains key configuration steps and strategies that you can use to implement a config plugin in your library.
## Strategic value of a config plugin in a library
Config plugins tend to solve interconnected problems that have historically made React Native library adoption more difficult than it should be. At times, when a user installs a React Native library, they face a complex set of native configuration steps that must be performed correctly for the library to function. These steps are platform-specific and sometimes require deep knowledge of native development concepts.
By creating a config plugin within your library, you can transform this complex-looking manual process into a simple configuration declaration that a user can apply in their Expo project's app config file (usually, **app.json**). This reduces the barrier to adoption for your library and simultaneously makes the setup process reliable.
Beyond immediate user experience improvements, config plugins enable compatibility with [Continuous Native Generation](/workflow/continuous-native-generation/), where native directories are generated automatically rather than checked into version control. Without a config plugin, developers who have adopted CNG face a difficult choice: either abandon the CNG workflow to manually configure native files, or invest significant effort in creating their own automation solutions. This creates a substantial barrier to library adoption in modern Expo development workflows.
## Project structure
A directory structure is the foundation for maintaining config plugins within your library. Below is an example directory structure:
```
âââ ./
    âââ android/  # Android native module code
    â   âââ src/
    â   â   âââ main/
    â   â       âââ java/
    â   â           âââ com/
    â   â               âââ your-awesome-library
    â   âââ build.gradle
    âââ ios/  # iOS native module code
    â   âââ YourAwesomeLibrary
    â   âââ YourAwesomeLibrary.podspec
    âââ src/
    â   âââ index.ts  # Main library entry point
    â   âââ YourAwesomeLibrary.ts  # Core library implementation
    â   âââ types.ts  # TypeScript type definitions
    âââ plugin/
    â   âââ src/
    â   â   âââ index.ts  # Plugin entry point
    â   â   âââ withAndroid.ts  # Android-specific configurations
    â   â   âââ withIos.ts  # iOS-specific configurations
    â   âââ build/
    â   â   âââ   # Compiled plugin output (generated)
    â   âââ __tests__/
    â   â   âââ   # Plugin-specific tests
    â   âââ tsconfig.json  # Plugin-specific TypeScript config
    âââ example/
    â   âââ app.json  # Example app configuration
    â   âââ App.tsx  # Example app implementation
    â   âââ package.json  # Example app dependencies
    âââ __tests__/
    â   âââ   # Main library tests
    âââ app.plugin.js  # Plugin entry point for Expo CLI
    âââ package.json  # Package configuration
    âââ tsconfig.json  # Main TypeScript configuration
    âââ jest.config.js  # Testing configuration
    âââ README.md  # Documentation
```
The directory structure example above highlights the following organizational principles:
- **Root-level separation**: Clear boundaries between library code (**src**) and plugin implementation (**plugin**)
- **Plugin directory organization**: Platform-specific files (**withAndroid.ts**, **withIos.ts**) enable focused testing and maintenance
- **Build output management**: Compiled JavaScript and TypeScript declarations in **plugins/build/** directory
- **Testing**: Separate plugin tests from library tests to reflect different concerns.
## Installation and configuration for development
The most straightforward approach to leverage Expo's tooling is to use `expo` and [`expo-module-scripts`](https://www.npmjs.com/package/expo-module-scripts).
- `expo` provides a config plugin API and types that your plugin will use.
- `expo-module-scripts` provides build tooling specifically designed for Expo modules and config plugins. It also handles TypeScript compilation.
```sh
$ npx expo install package
```
When using `expo-module-scripts`, it requires the following **package.json** configuration. For any already existing script with the same script name, replace it.
```json package.json
{
  "scripts": {
    "build": "expo-module build",
    "build:plugin": "expo-module build plugin",
    "clean": "expo-module clean",
    "test": "expo-module test",
    "prepare": "expo-module prepare",
    "prepublishOnly": "expo-module prepublishOnly"
  },
  "devDependencies": {
    "expo": "^54.0.0"
  },
  "peerDependencies": {
    "expo": ">=54.0.0"
  },
  "peerDependenciesMeta": {
    "expo": {
      "optional": true
    }
  }
}
```
The next step is to add TypeScript support within the **plugins** directory. Open **plugins/tsconfig.json** file and add the following:
```json plugins/tsconfig.json
{
  "extends": "expo-module-scripts/tsconfig.plugin",
  "compilerOptions": {
    "outDir": "build",
    "rootDir": "src"
  },
  "include": ["./src"],
  "exclude": ["**/__mocks__/*", "**/__tests__/*"]
}
```
You also need to define the main entry point for your config plugin in the **app.plugin.js** file, which exports the compiled plugin code from the **plugin/build** directory:
```js app.plugin.js
module.exports = require('./plugin/build');
```
The above configuration is essential because when the Expo CLI looks for a plugin, it checks for this file in the project root of your library. The **plugin/build** directory contains the JavaScript files generated from your config plugin's TypeScript source code.
## Key implementation patterns
Essential patterns for a successful config plugin implementation include:
- **Plugin structure**: Core patterns that every plugin should follow
- **Platform-specific implementations**: Handle Android and iOS configurations effectively
- **Test strategies:** Validating your plugin code through testing
### Plugin structure and platform-specific implementation
Every config plugin follows the same pattern: receives configuration and parameters, applies transformations through mods, and returns the modified configuration. Consider the following core plugin structure looks like:
For Index file: 
```ts plugin/src/index.ts|collapseHeight=480
import { type ConfigPlugin, withAndroidManifest, withInfoPlist } from 'expo/config-plugins';
export interface YourLibraryPluginProps {
  customProperty?: string;
  enableFeature?: boolean;
}
const withYourLibrary: ConfigPlugin<YourLibraryPluginProps> = (config, props = {}) => {
  // Apply Android configurations
  config = withAndroidConfiguration(config, props);
  // Apply iOS configurations
  config = withIosConfiguration(config, props);
  return config;
};
export default withYourLibrary;
```
For Android: 
```ts plugin/src/withAndroid.ts
import { type ConfigPlugin, withAndroidManifest, AndroidConfig } from 'expo/config-plugins';
export const withAndroidConfiguration: ConfigPlugin<YourLibraryPluginProps> = (config, props) => {
  return withAndroidManifest(config, config => {
    const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
    AndroidConfig.Manifest.addMetaDataItemToMainApplication(
      mainApplication,
      'your_library_config_key',
      props.customProperty || 'default_value'
    );
    return config;
  });
};
```
For iOS: 
```ts plugin/src/withIos.ts
import { type ConfigPlugin, withInfoPlist } from 'expo/config-plugins';
export const withIosConfiguration: ConfigPlugin<YourLibraryPluginProps> = (config, props) => {
  return withInfoPlist(config, config => {
    config.modResults.YourLibraryCustomProperty = props.customProperty || 'default_value';
    if (props.enableFeature) {
      config.modResults.YourLibraryFeatureEnabled = true;
    }
    return config;
  });
};
```
### Testing strategies
Config plugin testing differs from regular library testing because you are testing configuration transformations rather than runtime behavior. Your plugin receives configuration objects and returns modified configuration objects.
Effective testing for a config plugin can be a combination of one or more of the following:
- **Unit testing:** Test configuration transformation logic with mocked Expo configuration objects
- **Cross-platform validation**: Use an example app to verify the actual prebuild output
- **Error condition testing**: Use error handling
Since unit tests focus on a plugin's transformation logic without involving the file system, you can use Jest to create and run mock configuration objects, pass them through your plugin, and verify expected modifications are made correctly. For example:
```ts plugin/__tests__/withYourLibrary.test.ts
import { withYourLibrary } from '../src';
describe('withYourLibrary', () => {
  it('should configure Android with custom property', () => {
    const config = {
      name: 'test-app',
      slug: 'test-app',
      platforms: ['android', 'ios'],
    };
    const result = withYourLibrary(config, {
      customProperty: 'test-value',
    });
    // Verify the plugin was applied correctly
    expect(result.plugins).toBeDefined();
  });
});
```
Errors should be handled gracefully inside your config plugin to provide clear feedback when a configuration fails. Use `try-catch` blocks to intercept errors early:
```ts plugin/src/index.ts
const withYourLibrary: ConfigPlugin<YourLibraryPluginProps> = (config, props = {}) => {
  try {
    // Validate configuration early
    validateProps(props);
    // Apply configurations
    config = withAndroidConfiguration(config, props);
    config = withIosConfiguration(config, props);
    return config;
  } catch (error) {
    // Re-throw with more context if needed
    throw new Error(`Failed to configure YourLibrary plugin: ${error.message}`);
  }
};
```
## Alternative build approaches
If your library doesn't use `expo-module-scripts`, you have two options:
### Add a plugin to your main package
For libraries using different build tools (like those created with `create-react-native-library`), add an **app.plugin.js** file and build it along with your main package:
```js app.plugin.js
module.exports = require('./lib/plugin');
```
### Create a separate plugin package
Some libraries distribute their config plugin as a separate package from their main library. This approach allows you to maintain your config plugin separately from the rest of your native module. You need to include export in **app.plugin.js** and compile the **build** directory from your plugin.
```js app.plugin.js
{
  "name": "your-library-expo-plugin",
  "main": "app.plugin.js",
  "files": ["app.plugin.js", "build/"],
  "peerDependencies": {
    "expo": "*",
    "your-library": "*"
  }
}
```
## Plugin development best practices
- **Instructions in your README**: If the plugin is tied to a React Native module, then you should document manual setup instructions for the package. If anything goes wrong with the plugin, developers should be able to manually add the project modifications that were automated by the plugin. This also allows you to support projects that are not using [CNG](/workflow/continuous-native-generation/).
  - Document the available properties for the plugin, specifying if any of the properties are required.
  - If possible, plugins should be idempotent, meaning the changes they make are the same whether they are run on a fresh native project template or run again on a project template where its changes already exist. This allows developers to run `npx expo prebuild` without the `--clean` flag to sync changes to the config, rather than recreating the native project entirely. This may be more difficult with dangerous mods.
- **Naming conventions**: Use `withFeatureName` for the plugin function name if it applies to all platforms. If the plugin is platform-specific, use a camel case naming with the platform right after "with". For example, `withAndroidSplash`, `withIosSplash`.
- **Leverage built-in plugins**: If there's already a configuration available in [app config](/versions/latest/config/app/) and [prebuild config](https://github.com/expo/expo/blob/main/packages/%40expo/prebuild-config/src/plugins/withDefaultPlugins.ts), you don't need to write a config plugin for it.
- **Split up plugins by platform**: When using functions within the config plugin, split them by platform. For example, `withAndroidSplash`, `withIosSplash`. This makes using the `--platform` flag in `npx expo prebuild` a bit easier to follow in `EXPO_DEBUG` mode, as the logging will show which platform-specific functions are being executed.
- **Unit test your plugin**: Write Jest tests for complex modifications. If your plugin requires access to the filesystem,
  use a mock system (we strongly recommend [`memfs`](https://www.npmjs.com/package/memfs)), you can see examples of this in the [`expo-notifications`](https://github.com/expo/expo/blob/fc3fb2e81ad3a62332fa1ba6956c1df1c3186464/packages/expo-notifications/plugin/src/__tests__/withNotificationsAndroid-test.ts#L34) plugin tests.
  - Notice the root [\*\*/\_\_mocks\_\_/\*\*/\*](https://github.com/expo/expo/tree/main/packages/expo-notifications/plugin/__mocks__) directory and [**plugin/jest.config.js**](https://github.com/expo/expo/tree/main/packages/expo-notifications/plugin/jest.config.js).
- A TypeScript plugin is always preferable to a JavaScript due to added type-safety. Check out the [`expo-module-scripts` plugin](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin) tooling for more info.
- Do not modify the `sdkVersion` via a config plugin, this can break commands like `expo install` and cause other unexpected issues.


## Developing and debugging a plugin

Learn about development best practices and debugging techniques for Expo config plugins.

Developing a plugin is a great way to extend the Expo ecosystem. However, there are times you'll want to debug your plugin. This page provides some of the best practices for developing and debugging a plugin.
## Plugin development
> Use [modifier previews](https://github.com/expo/vscode-expo#expo-preview-modifier) to debug the results of your plugin live.
To make plugin development easier, we've added plugin support to [`expo-module-scripts`](https://www.npmjs.com/package/expo-module-scripts).
Refer to the [config plugins guide](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin) for more info on using TypeScript, and Jest to build plugins.
### Install dependencies
Use the following dependencies in a library that provides a config plugin:
```json package.json
{
  "dependencies": {},
  "devDependencies": {
    "expo": "^54.0.0"
  },
  "peerDependencies": {
    "expo": ">=54.0.0"
  },
  "peerDependenciesMeta": {
    "expo": {
      "optional": true
    }
  }
}
```
- You may update the exact version of `expo` to build against a specific version.
- For simple config plugins that depend on core, stable APIs, such as a plugin that only modifies **AndroidManifest.xml** or **Info.plist**, you can use a loose dependency such as in the example above.
- You may also want to install [`expo-module-scripts`](https://github.com/expo/expo/blob/main/packages/expo-module-scripts/README.md) as a development dependency, but it's not required.
### Import the config plugins package
The `expo/config-plugins` and `expo/config` packages are re-exported from the `expo` package.
```js
const { ... } = require('expo/config-plugins');
const { ... } = require('expo/config');
```
Importing through the `expo` package ensures that you are using the version of the `expo/config-plugins` and `expo/config` packages that are depended on by the `expo` package.
If you do not import the package through the `expo` re-export in this way, you may accidentally be importing an incompatible version
(depending on the implementation details of module hoisting in the package manager used by the developer consuming the module) or be unable to import the module at all (if using "plug and play" features of a package manager such as Yarn Berry or pnpm).
Config types are exported directly from `expo/config`, so there is no need to install or import from `expo/config-types`:
```ts
import { ExpoConfig, ConfigContext } from 'expo/config';
```
### Best practices for mods
- Avoid regex: [static modification](#static-modification) is key. If you want to modify a value in an Android gradle file, consider using `gradle.properties`. If you want to modify some code in the Podfile, consider writing to JSON and having the Podfile read the static values.
- Avoid performing long-running tasks like making network requests or installing Node modules in mods.
- Do not add interactive terminal prompts in mods.
- Generate, move, and delete new files in dangerous mods only. Failing to do so will break [introspection](#introspection).
- Utilize built-in config plugins like `withXcodeProject` to minimize the amount of times a file is read and parsed.
- Stick with the XML parsing libraries that prebuild uses internally, this helps prevent changes where code is rearranged needlessly.
## Plugin structure and scaffolding
### Versioning
By default, `npx expo prebuild` runs transformations on a [source template](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum) associated with the Expo SDK version that a project is using. The SDK version is defined in the **app.json** or inferred from the installed version of `expo` that the project has.
When Expo SDK upgrades to a new version of React Native for instance, the template may change significantly to account for changes in React Native or new releases of Android or iOS.
If your plugin is mostly using [static modifications](#static-modification) then it will usually work well across SDK versions. If it's using a regular expression to transform application code, then you'll definitely want to document which Expo SDK version your plugin is intended for. During the SDK release cycle, there is a [beta period](https://github.com/expo/expo/blob/main/guides/releasing/Release%20Workflow.md#stage-4---beta-release) where you can test if your plugin works with the new version before it's released.
### Plugin properties
Properties are used to customize the way a plugin works during prebuild. They must always be static values (no functions, or promises). Consider the following types:
```ts
type StaticValue = boolean | number | string | null | StaticArray | StaticObject;
type StaticArray = StaticValue[];
interface StaticObject {
  [key: string]: StaticValue | undefined;
}
```
Static properties are required because the app config must be serializable to JSON for use as the app manifest.
If possible, attempt to make your plugin work without props, this will help resolution tooling like [`expo install`](#expo-install) or [VS Code Expo Tools](https://marketplace.visualstudio.com/items?itemName=expo.vscode-expo-tools) work better. Remember that every property you add increases complexity, making it harder to change in the future and increases the amount of features you'll need to test. Good default values are preferred over mandatory configuration when feasible.
## Development environment
### Tooling
We highly recommend installing the [Expo Tools VS Code extension](https://marketplace.visualstudio.com/items?itemName=expo.vscode-expo-tools) as this will perform automatic validation on the plugins and surface error information along with other quality of life improvements for Config Plugin development.
### Set up a playground environment
You can develop plugins easily using JS, but if you want to set up Jest tests and use TypeScript, you will want a monorepo.
A monorepo will enable you to work on a node module and import it in your app config like you would if it were published to npm. Expo config plugins have full monorepo support built-in so all you need to do is set up a project.
In your monorepo's `packages/` directory, create a module, and [bootstrap a config plugin](https://github.com/expo/expo/tree/main/packages/expo-module-scripts#-config-plugin) in it.
### Manually run a plugin
If you aren't comfortable setting up a monorepo, you can try manually running a plugin:
- Run `npm pack` in the package with the config plugin
- In your test project, run `npm install path/to/react-native-my-package-1.0.0.tgz`, this will add the package to your **package.json** `dependencies` object.
- Add the package to the `plugins` array in your **app.json**: `{ "plugins": ["react-native-my-package"] }`
  - If you have [VS Code Expo Tools](https://marketplace.visualstudio.com/items?itemName=expo.vscode-expo-tools) installed, autocomplete should work for the plugin.
- If you need to update the package, change the `version` in the package's **package.json** and repeat the process.
## Modifying native files with plugins
### Modify AndroidManifest.xml
Packages should attempt to use the built-in **AndroidManifest.xml** [merging system](https://developer.android.com/studio/build/manage-manifests) before using a config plugin. This can be used for static, non-optional features like permissions. This will ensure features are merged during build-time and not prebuild-time, which minimizes the possibility of the configuration being missed due to users forgetting to prebuild. The drawback is that users cannot use [introspection](#introspection) to preview the changes and debug any potential issues.
Here is an example of a package's **AndroidManifest.xml**, which injects a required permission:
```xml AndroidManifest.xml
<!-- @info Include <code>xmlns:android="..."</code> to use <code>android:*</code> properties like <code>android:name</code> in your manifest. -->
<manifest package="expo.modules.filesystem" xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- @end -->
  <uses-permission android:name="android.permission.INTERNET"/>
</manifest>
```
If you're building a plugin for your local project, or if your package needs more control, then you should implement a plugin.
You can use built-in types and helpers to ease the process of working with complex objects.
Here's an example of adding a `<meta-data android:name="..." android:value="..."/>` to the default `<application android:name=".MainApplication" />`.
```ts my-config-plugin.ts
import { AndroidConfig, ConfigPlugin, withAndroidManifest } from 'expo/config-plugins';
import { ExpoConfig } from 'expo/config';
// Using helpers keeps error messages unified and helps cut down on XML format changes.
const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest;
export const withMyCustomConfig: ConfigPlugin = config => {
  return withAndroidManifest(config, async config => {
    // Modifiers can be async, but try to keep them fast.
    config.modResults = await setCustomConfigAsync(config, config.modResults);
    return config;
  });
};
// Splitting this function out of the mod makes it easier to test.
async function setCustomConfigAsync(
  config: Pick<ExpoConfig, 'android'>,
  androidManifest: AndroidConfig.Manifest.AndroidManifest
): Promise<AndroidConfig.Manifest.AndroidManifest> {
  const appId = 'my-app-id';
  // Get the <application /> tag and assert if it doesn't exist.
  const mainApplication = getMainApplicationOrThrow(androidManifest);
  addMetaDataItemToMainApplication(
    mainApplication,
    // value for `android:name`
    'my-app-id-key',
    // value for `android:value`
    appId
  );
  return androidManifest;
}
```
### Modify Info.plist
Using the `withInfoPlist` is a bit safer than statically modifying the `expo.ios.infoPlist` object in the **app.json** because it reads the contents of the Info.plist and merges it with the `expo.ios.infoPlist`, this means you can attempt to keep your changes from being overwritten.
Here's an example of adding a `GADApplicationIdentifier` to the **Info.plist**:
```ts my-config-plugin.ts
import { ConfigPlugin, withInfoPlist } from 'expo/config-plugins';
// Pass `<string>` to specify that this plugin requires a string property.
export const withCustomConfig: ConfigPlugin<string> = (config, id) => {
  return withInfoPlist(config, config => {
    config.modResults.GADApplicationIdentifier = id;
    return config;
  });
};
```
### Modify iOS Podfile
The iOS **Podfile** is the config file for CocoaPods, the dependency manager on iOS. It is similar to **package.json** for iOS.
The **Podfile** is a Ruby file, which means you **cannot** safely modify it from Expo config plugins and should opt for another approach, such as [Expo Autolinking](/modules/autolinking) hooks.
We do expose one mechanism for safely interacting with the Podfile, but it's very limited.
The versioned [template Podfile](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum/ios/Podfile) is hard coded to read
from a static JSON file **Podfile.properties.json**, we expose a mod (`ios.podfileProperties`, `withPodfileProperties`) to safely read and write from this file.
This is used by [expo-build-properties](/versions/latest/sdk/build-properties) and to configure the JavaScript engine.
### Add plugins to `pluginHistory`
`_internal.pluginHistory` was created to prevent duplicate plugins from running while migrating from legacy UNVERSIONED plugins to versioned plugins.
```ts my-config-plugin.ts
import { ConfigPlugin, createRunOncePlugin } from 'expo/config-plugins';
// Keeping the name, and version in sync with it's package.
const pkg = require('my-cool-plugin/package.json');
const withMyCoolPlugin: ConfigPlugin = config => config;
// A helper method that wraps `withRunOnce` and appends items to `pluginHistory`.
export default createRunOncePlugin(
  // The plugin to guard.
  withMyCoolPlugin,
  // An identifier used to track if the plugin has already been run.
  pkg.name,
  // Optional version property, if omitted, defaults to UNVERSIONED.
  pkg.version
);
```
### Configure Android app startup
You may find that your project requires configuration to be setup before the JS engine has started.
For example, in `expo-splash-screen` on Android, we need to specify the resize mode in the **MainActivity.java**'s `onCreate` method.
Instead of attempting to dangerously regex these changes into the `MainActivity` via a dangerous mod, we use a system of lifecycle hooks and static settings
to safely ensure the feature works across all supported Android languages (Java, Kotlin), versions of Expo, and combination of config plugins.
This system is made up of three components:
- `ReactActivityLifecycleListeners`: An interface exposed by `expo-modules-core` to get a native callback when the project `ReactActivity`'s `onCreate` method is invoked.
- `withStringsXml`: A mod exposed by `expo/config-plugins` which writes a property to the Android **strings.xml** file, the library can safely read the strings.xml value and do initial setup. The string XML values follow a designated format for consistency.
- `SingletonModule` (optional): An interface exposed by `expo-modules-core` to create a shared interface between native modules and `ReactActivityLifecycleListeners`.
Consider this example: We want to set a custom "value" string to a property on the Android `Activity`, directly after the `onCreate` method was invoked.
We can do this safely by creating a node module `expo-custom`, implementing `expo-modules-core`, and Expo config plugins:
First, we register the `ReactActivity` listener in our Android native module, this will only be invoked if the user has `expo-modules-core` support, setup in their project (default in projects bootstrapped with Expo CLI, Create React Native App, Ignite CLI, and Expo prebuilding).
```kotlin expo-custom/android/src/main/java/expo/modules/custom/CustomPackage.kt
package expo.modules.custom
import android.content.Context
import expo.modules.core.BasePackage
import expo.modules.core.interfaces.ReactActivityLifecycleListener
class CustomPackage : BasePackage() {
  override fun createReactActivityLifecycleListeners(activityContext: Context): List<ReactActivityLifecycleListener> {
    return listOf(CustomReactActivityLifecycleListener(activityContext))
  }
  // ...
}
```
Next we implement the `ReactActivity` listener, this is passed the `Context` and is capable of reading from the project **strings.xml** file.
```kotlin expo-custom/android/src/main/java/expo/modules/custom/CustomReactActivityLifecycleListener.kt
package expo.modules.custom
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.util.Log
import expo.modules.core.interfaces.ReactActivityLifecycleListener
class CustomReactActivityLifecycleListener(activityContext: Context) : ReactActivityLifecycleListener {
  override fun onCreate(activity: Activity, savedInstanceState: Bundle?) {
    // Execute static tasks before the JS engine starts.
    // These values are defined via config plugins.
    var value = getValue(activity)
    if (value != "") {
      // Do something to the Activity that requires the static value...
    }
  }
  // Naming is node module name (`expo-custom`) plus value name (`value`) using underscores as a delimiter
  // i.e. `expo_custom_value`
  // `@expo/vector-icons` + `iconName` -> `expo__vector_icons_icon_name`
  private fun getValue(context: Context): String = context.getString(R.string.expo_custom_value).toLowerCase()
}
```
We must define default **string.xml** values which the user will overwrite locally by using the same `name` property in their **strings.xml** file.
```xml expo-custom/android/src/main/res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="expo_custom_value" translatable="false"></string>
</resources>
```
At this point, bare users can configure this value by creating a string in their local **strings.xml** file (assuming they also have `expo-modules-core` support setup):
```xml ./android/app/src/main/res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="expo_custom_value" translatable="false">I Love Expo</string>
</resources>
```
For managed users, we can expose this functionality (safely!) via an Expo config plugin:
```js expo-custom/app.plugin.js
const { AndroidConfig, withStringsXml } = require('expo/config-plugins');
function withCustom(config, value) {
  return withStringsXml(config, config => {
    config.modResults = setStrings(config.modResults, value);
    return config;
  });
}
function setStrings(strings, value) {
  // Helper to add string.xml JSON items or overwrite existing items with the same name.
  return AndroidConfig.Strings.setStringItem(
    [
      // XML represented as JSON
      // <string name="expo_custom_value" translatable="false">value</string>
      { $: { name: 'expo_custom_value', translatable: 'false' }, _: value },
    ],
    strings
  );
}
```
Managed Expo users can now interact with this API like so:
```json app.json
{
  "expo": {
    "plugins": [["expo-custom", "I Love Expo"]]
  }
}
```
By re-running `npx expo prebuild -p` (`eas build -p android`, or `npx expo run:ios`) the user can now see the changes, safely applied in their managed project!
As you can see from the example, we rely heavily on application code (expo-modules-core) to interact with application code (the native project). This ensures that our config plugins are safe and reliable, hopefully for a very long time!
## Debugging config plugins
You can debug config plugins by running `EXPO_DEBUG=1 expo prebuild`. If `EXPO_DEBUG` is enabled, the plugin stack logs will be printed, these are useful for viewing which mods ran, and in what order they ran in. To view all static plugin resolution errors, enable `EXPO_CONFIG_PLUGIN_VERBOSE_ERRORS`, this should only be needed for plugin authors. By default, some automatic plugin errors are hidden because they're usually related to versioning issues and aren't very helpful (that is, legacy package doesn't have a config plugin yet).
Running `npx expo prebuild --clean` will remove the generated native directories before compiling.
You can also run `npx expo config --type prebuild` to print the results of the plugins with the mods unevaluated (no code is generated).
Expo CLI commands can be profiled using `EXPO_PROFILE=1`.
## Introspection
Introspection is an advanced technique used to read the evaluated results of modifiers without generating any code in the project.
This can be used to quickly debug the results of [static modifications](#static-modification) without needing to run prebuild.
You can interact with introspection live, by using the [preview feature](https://github.com/expo/vscode-expo#expo-preview-modifier) of `vscode-expo`.
You can try introspection by running `expo config --type introspect` in a project.
Introspection only supports a subset of modifiers:
- `android.manifest`
- `android.gradleProperties`
- `android.strings`
- `android.colors`
- `android.colorsNight`
- `android.styles`
- `ios.infoPlist`
- `ios.entitlements`
- `ios.expoPlist`
- `ios.podfileProperties`
> Introspection only works on safe modifiers (static files like JSON, XML, plist, properties), except `ios.xcodeproj` which often requires file system changes, making it non idempotent.
Introspection works by creating custom base mods that work like the default base mods, except they don't write the `modResults` to disk at the end.
Instead of persisting, they save the results to the app config under `_internal.modResults`, followed by the name of the mod
such as the `ios.infoPlist` mod saves to `_internal.modResults.ios.infoPlist: {}`.
As a real-world example, introspection is used by `eas-cli` to determine what the final iOS entitlements will be in a managed app, so it can sync them with the Apple Developer Portal before building. Introspection can also be used as a handy debugging and development tool.
## Legacy plugins
To make `eas build` work the same as the classic `expo build` service, we added support for "legacy plugins" which are applied automatically to a project when they're installed in the project.
For instance, say a project has `expo-camera` installed but doesn't have `plugins: ['expo-camera']` in their **app.json**.
Expo CLI would automatically add `expo-camera` to the plugins to ensure that the required camera and microphone permissions are added to the project.
The user can still customize the `expo-camera` plugin by adding it to the `plugins` array manually, and the manually defined plugins will take precedence over the automatic plugins.
You can debug which plugins were added by running `expo config --type prebuild` and seeing the `_internal.pluginHistory` property.
This will show an object with all plugins that were added using `withRunOnce` plugin from `expo/config-plugins`.
Notice that `expo-location` uses `version: '11.0.0'`, and `react-native-maps` uses `version: 'UNVERSIONED'`. This means the following:
- `expo-location` and `react-native-maps` are both installed in the project.
- `expo-location` is using the plugin from the project's `node_modules/expo-location/app.plugin.js`
- The version of `react-native-maps` installed in the project doesn't have a plugin, so it's falling back on the unversioned plugin that is shipped with `expo-cli` for legacy support.
```json
{
  _internal: {
    pluginHistory: {
      'expo-location': {
        name: 'expo-location',
        version: '11.0.0',
      },
      'react-native-maps': {
        name: 'react-native-maps',
        version: 'UNVERSIONED',
      },
    },
  },
};
```
For the most _stable_ experience, you should try to have no `UNVERSIONED` plugins in your project. This is because the `UNVERSIONED` plugin may not support the native code in your project.
For instance, say you have an `UNVERSIONED` Facebook plugin in your project, if the Facebook native code or plugin has a breaking change, that will break the way your project prebuilds and cause it to error on build.
## Static modification
Plugins can transform application code with regular expressions, but these modifications are dangerous if the template changes over time then the regex becomes hard to predict (similarly if the user modifies a file manually or uses a custom template). Here are some examples of files you shouldn't modify manually, and alternatives.
### Android Gradle Files
Gradle files are written in either Groovy or Kotlin. They are used to manage dependencies, versioning, and other settings in the Android app.
Instead of modifying them directly with the `withProjectBuildGradle`, `withAppBuildGradle`, or `withSettingsGradle` mods, utilize the static `gradle.properties` file.
The `gradle.properties` is a static key/value pair that groovy files can read from. For example, say you wanted to control some toggle in Groovy:
```properties gradle.properties