---
modificationDate: September 10, 2025
title: Create and use config plugins
description: Learn how to create and use a config plugins in your Expo project.
---

<AgentInstructions>

## Submitting Feedback

If this page contains errors, outdated information, or gaps that blocked you from completing a task, report it so the docs can be improved:

curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/config-plugins/plugins/","feedback":"🤖 Agent feedback: <specific, actionable description>"}'

Only submit when you have something specific and actionable to report.

</AgentInstructions>

# Create and use config plugins

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

> For the complete documentation index, see [llms.txt](/llms.txt). Use this file to discover all available pages.

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:

```
withMyPlugin ("myPlugin") [Config Plugin]
→ withAndroidPlugin, withIosPlugin [Plugin Function]
→ withAndroidManifest, withInfoPlist [Mod Plugin Function]
→ mods.android.manifest, mods.ios.infoplist [Mod]
```

> **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`

### Create Android plugin

In **withAndroidPlugin.ts**, add the following code:

```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.

### Create iOS plugin

In **withIosPlugin.ts**, add the following code:

```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.

### 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
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;
```

### 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
import 'tsx/cjs';

module.exports = () => {
  ... rest of your app config 
};
```

### 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
import "tsx/cjs";
import { ExpoConfig } from "expo/config";

module.exports = ({ config }: { config: ExpoConfig }) => {
  ... rest of your app 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.

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
...
type AndroidProps = {
  message?: string;
};

const withAndroidPlugin: ConfigPlugin<AndroidProps> = (
  config,
  options = {}
) => {
  const message = options.message || 'Hello world, from Expo plugin!';
  return withAndroidManifest(config, config => {
   ... rest of the example remains unchanged 
  });
};

export default withAndroidPlugin;
```

Similarly, add an `options` object in **withIosPlugin.ts** and update the `message` variable to use the `options.message` property:

```ts
...
type IosProps = {
  message?: string;
};

const withIosPlugin: ConfigPlugin<IosProps> = (config, options = {}) => {
   const message = options.message || 'Hello world, from Expo plugin!';
  ... rest of the example remains unchanged
};

export default withIosPlugin;
```

Update the **withPlugin.ts** file to pass the `options` object to both plugins:

```ts
...
const withPlugin: ConfigPlugin<{ message?: string }> = (config, options = {}) => {
  config = withAndroidPlugin(config, options);
  return withIosPlugin(config, options);
};
```

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
{
  ...
  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
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
import { withPlugins } from 'expo/config-plugins';

// Create a base config object
const baseConfig = {
  name: 'my app',
  ... rest of the config 
};

// ❌ 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
{
  "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
{
  "expo": {
    "plugins": [
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera."
        }
      ]
    ]
  }
}
```

> **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.
