1---
2title: Plugins and mods
3description: Learn about what are plugins and mods when creating a config plugin.
4---
5
6import { YesIcon, NoIcon, WarningIcon } from '~/ui/components/DocIcons';
7import { Collapsible } from '~/ui/components/Collapsible';
8import { BoxLink } from '~/ui/components/BoxLink';
9
10Plugins are **synchronous** functions that accept an [`ExpoConfig`](/versions/latest/config/app/) and return a modified [`ExpoConfig`](/versions/latest/config/app/).
11
12- Plugins should be named using the following convention: `with<Plugin Functionality>`, for example, `withFacebook`.
13- Plugins should be synchronous and their return value should be serializable, except for any `mods` that are added.
14- Optionally, a second argument can be passed to the plugin to configure it.
15- `plugins` are always invoked when the config is read by the `expo/config` method `getConfig`. However, the `mods` are only invoked during the "syncing" phase of `npx expo prebuild`.
16
17## Create a plugin
18
19Here is an example of the most basic config plugin:
20
21```js
22const withNothing = config => config;
23```
24
25Say you wanted to create a plugin that added custom values to **Info.plist** in an iOS project:
26
27```js my-plugin.js
28const withMySDK = (config, { apiKey }) => {
29  // Ensure the objects exist
30  if (!config.ios) {
31    config.ios = {};
32  }
33  if (!config.ios.infoPlist) {
34    config.ios.infoPlist = {};
35  }
36
37  // Append the apiKey
38  config.ios.infoPlist['MY_CUSTOM_NATIVE_IOS_API_KEY'] = apiKey;
39
40  return config;
41};
42
43// �� Usage:
44
45/// Create a config
46const config = {
47  name: 'my app',
48};
49
50/// Use the plugin
51export default withMySDK(config, { apiKey: 'X-XXX-XXX' });
52```
53
54## Import a plugin
55
56You may want to create a plugin in a different file, here's how:
57
58- The root file can be any JS file or a file named **app.plugin.js** in the root of a Node module.
59- The file should export a function that satisfies the [`ConfigPlugin`](https://github.com/expo/expo-cli/blob/3a0ef962a27525a0fe4b7e5567fb7b3fb18ec786/packages/config-plugins/src/Plugin.types.ts#L76) type.
60- Plugins should be transpiled for Node environments ahead of time!
61  - They should support the versions of Node that [Expo supports](/get-started/installation/#requirements) (LTS).
62  - No `import/export` keywords, use `module.exports` in the shipped plugin file.
63  - Expo only transpiles the user's initial `app.config` file, anything more would require a bundler which would add too many "opinions" for a config file.
64
65Consider the following example that changes the config name:
66
67```
68╭── app.config.js ➡️ Expo Config
69╰── my-plugin.js ➡️ Our custom plugin file
70```
71
72```js my-plugin.js
73module.exports = function withPrefixedName(config, prefix) {
74  // Modify the config
75  config.name = prefix + '-' + config.name;
76  // Return the results
77  return config;
78};
79```
80
81```js app.config.js
82{
83  "name": "my-app",
84  "plugins": [["./my-plugin", "custom"]]
85}
86```
87
88It evaluates to the following JSON config:
89
90```json Evaluated config JSON
91{
92  "name": "custom-my-app",
93  "plugins": [["./my-plugin", "custom"]]
94}
95```
96
97## Chain plugins
98
99Once you add a few plugins, your **app.config.js** code can become difficult to read and manipulate. To combat this, `expo/config-plugins` provides a `withPlugins` function which can be used to chain plugins together and execute them in order.
100
101```js app.config.js
102/// Create a config
103const config = {
104  name: 'my app',
105};
106
107// ❌ Hard to read
108withDelta(withFoo(withBar(config, 'input 1'), 'input 2'), 'input 3');
109
110// ✅ Easy to read
111import { withPlugins } from 'expo/config-plugins';
112
113withPlugins(config, [
114  [withBar, 'input 1'],
115  [withFoo, 'input 2'],
116  // When no input is required, you can just pass the method...
117  withDelta,
118]);
119```
120
121<Collapsible summary="Using SDK 46 or lower?">
122
123For SDK 46 and lower, import the `@expo/config-plugins` package directly. This is installed automatically by the `expo` package, but not re-exported as it is in SDK 47 and higher.
124
125```js app.config.js
126const { withPlugins } = require('@expo/config-plugins');
127```
128
129</Collapsible>
130
131To support JSON configs, we also added the `plugins` array which just uses `withPlugins` under the hood.
132Here is the same config as above, but even simpler:
133
134```js app.config.js
135export default {
136  name: 'my app',
137  plugins: [
138    [withBar, 'input 1'],
139    [withFoo, 'input 2'],
140    [withDelta, 'input 3'],
141  ],
142};
143```
144
145## What are mods
146
147A modifier (mod for short) is an async function that accepts a config and a data object, then manipulates and returns both as an object.
148
149Mods are added to the `mods` object of the Expo config. The `mods` object is different from the rest of the Expo config because it doesn't get serialized
150after the initial reading, which means you can use it to perform actions _during_ code generation.
151If possible, you should attempt to use basic plugins instead of mods, as they're simpler to work with.
152
153- `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!
154- `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, etc...
155- `mods` are platform-specific and should always be added to a platform-specific object:
156
157```js app.config.js
158module.exports = {
159  name: 'my-app',
160  mods: {
161    ios: {
162      /* iOS mods... */
163    },
164    android: {
165      /* Android mods... */
166    },
167  },
168};
169```
170
171## How mods work
172
173- The config is read using [`getPrebuildConfig`](https://github.com/expo/expo-cli/blob/43a6162edd646b550c1b7eae6039daf1aaec4fb0/packages/prebuild-config/src/getPrebuildConfig.ts#L12) from `@expo/prebuild-config`.
174- All of the core functionality supported by Expo is added via plugins in `withIosExpoPlugins`. This is stuff like name, version, icons, locales, etc.
175- The config is passed to the compiler `compileModsAsync`
176- 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.
177- The compiler iterates over all the mods and asynchronously evaluates them, providing some base props like the `projectRoot`.
178  - After each mod, error handling asserts if the mod chain was corrupted by an invalid mod.
179
180{/* TODO: Move to a section about mod compiler */}
181
182### Default mods
183
184The following default mods are provided by the mod compiler for common file manipulation.
185
186> Dangerous modifications rely on regular expressions (regex) to modify application code, which may cause the build to break.
187> Regex mods are also difficult to version, and therefore should be used sparingly.
188> Always opt towards using application code to modify application code, that is, [Expo Modules](https://github.com/expo/expo/tree/main/packages/expo-modules-core) native API.
189
190| Android mod                       |    Dangerous    | Description                                                                                               |
191| --------------------------------- | :-------------: | --------------------------------------------------------------------------------------------------------- |
192| `mods.android.manifest`           |        -        | Modify the **android/app/src/main/AndroidManifest.xml** as JSON (parsed with [`xml2js`][xml2js]).         |
193| `mods.android.strings`            |        -        | Modify the **android/app/src/main/res/values/strings.xml** as JSON (parsed with [`xml2js`][xml2js]).      |
194| `mods.android.colors`             |        -        | Modify the **android/app/src/main/res/values/colors.xml** as JSON (parsed with [`xml2js`][xml2js]).       |
195| `mods.android.colorsNight`        |        -        | Modify the **android/app/src/main/res/values-night/colors.xml** as JSON (parsed with [`xml2js`][xml2js]). |
196| `mods.android.styles`             |        -        | Modify the **android/app/src/main/res/values/styles.xml** as JSON (parsed with [`xml2js`][xml2js]).       |
197| `mods.android.gradleProperties`   |        -        | Modify the **android/gradle.properties** as a `Properties.PropertiesItem[]`.                              |
198| `mods.android.mainActivity`       | <WarningIcon /> | Modify the **android/app/src/main/&lt;package&gt;/MainActivity.java** as a string.                        |
199| `mods.android.mainApplication`    | <WarningIcon /> | Modify the **android/app/src/main/&lt;package&gt;/MainApplication.java** as a string.                     |
200| `mods.android.appBuildGradle`     | <WarningIcon /> | Modify the **android/app/build.gradle** as a string.                                                      |
201| `mods.android.projectBuildGradle` | <WarningIcon /> | Modify the **android/build.gradle** as a string.                                                          |
202| `mods.android.settingsGradle`     | <WarningIcon /> | Modify the **android/settings.gradle** as a string.                                                       |
203
204| iOS mod                      |    Dangerous    | Description                                                                                                                         |
205| ---------------------------- | :-------------: | ----------------------------------------------------------------------------------------------------------------------------------- |
206| `mods.ios.infoPlist`         |        -        | Modify the **ios/&lt;name&gt;/Info.plist** as JSON (parsed with [`@expo/plist`][expo-plist]).                                       |
207| `mods.ios.entitlements`      |        -        | Modify the **ios/&lt;name&gt;/&lt;product-name&gt;.entitlements** as JSON (parsed with [`@expo/plist`][expo-plist]).                |
208| `mods.ios.expoPlist`         |        -        | Modify the **ios/&lt;ame&gt;/Expo.plist** as JSON (Expo updates config for iOS) (parsed with [`@expo/plist`][expo-plist]).          |
209| `mods.ios.xcodeproj`         |        -        | Modify the **ios/&lt;name&gt;.xcodeproj** as an `XcodeProject` object (parsed with [`xcode`](https://www.npmjs.com/package/xcode)). |
210| `mods.ios.podfileProperties` |        -        | Modify the **ios/Podfile.properties.json** as JSON.                                                                                 |
211| `mods.ios.appDelegate`       | <WarningIcon /> | Modify the **ios/&lt;name&gt;/AppDelegate.m** as a string.                                                                          |
212
213After the mods are resolved, the contents of each mod will be written to disk. Custom default mods can be added to support new native files.
214For example, you can create a mod to support the `GoogleServices-Info.plist`, and pass it to other mods.
215
216### Mod plugins
217
218Mods are responsible for a lot of tasks, so they can be pretty difficult to understand at first.
219If you're developing a feature that requires mods, it's best not to interact with them directly.
220
221Instead you should use the helper mods provided by `expo/config-plugins`:
222
223#### Android
224
225| Android mod                       | Mod plugin               |    Dangerous    |
226| --------------------------------- | ------------------------ | :-------------: |
227| `mods.android.manifest`           | `withAndroidManifest`    |        -        |
228| `mods.android.strings`            | `withStringsXml`         |        -        |
229| `mods.android.colors`             | `withAndroidColors`      |        -        |
230| `mods.android.colorsNight`        | `withAndroidColorsNight` |        -        |
231| `mods.android.styles`             | `withAndroidStyles`      |        -        |
232| `mods.android.gradleProperties`   | `withGradleProperties`   |        -        |
233| `mods.android.mainActivity`       | `withMainActivity`       | <WarningIcon /> |
234| `mods.android.mainApplication`    | `withMainApplication`    | <WarningIcon /> |
235| `mods.android.appBuildGradle`     | `withAppBuildGradle`     | <WarningIcon /> |
236| `mods.android.projectBuildGradle` | `withProjectBuildGradle` | <WarningIcon /> |
237| `mods.android.settingsGradle`     | `withSettingsGradle`     | <WarningIcon /> |
238
239#### iOS
240
241| iOS mod                      | Mod plugin              |    Dangerous    |
242| ---------------------------- | ----------------------- | :-------------: |
243| `mods.ios.infoPlist`         | `withInfoPlist`         |        -        |
244| `mods.ios.entitlements`      | `withEntitlementsPlist` |        -        |
245| `mods.ios.expoPlist`         | `withExpoPlist`         |        -        |
246| `mods.ios.xcodeproj`         | `withXcodeProject`      |        -        |
247| `mods.ios.podfileProperties` | `withPodfileProperties` |        -        |
248| `mods.ios.appDelegate`       | `withAppDelegate`       | <WarningIcon /> |
249
250A mod plugin gets passed a `config` object with additional properties `modResults` and `modRequest` added to it.
251
252- `modResults`: The object to modify and return. The type depends on the mod that's being used.
253- `modRequest`: Additional properties supplied by the mod compiler.
254  - `projectRoot: string`: Project root directory for the universal app.
255  - `platformProjectRoot: string`: Project root for the specific platform.
256  - `modName: string`: Name of the mod.
257  - `platform: ModPlatform`: Name of the platform used in the mods config.
258  - `projectName?: string`: (iOS only) The path component used for querying project files. ex. `projectRoot/ios/[projectName]/`
259
260## Create a mod
261
262Say you wanted to write a mod to update the Xcode Project's "product name":
263
264```js my-config-plugin.js
265import { ConfigPlugin, withXcodeProject } from 'expo/config-plugins';
266
267const withCustomProductName: ConfigPlugin = (config, customName) => {
268  return withXcodeProject(config, async config => {
269    // config = { modResults, modRequest, ...expoConfig }
270
271    const xcodeProject = config.modResults;
272    xcodeProject.productName = customName;
273
274    return config;
275  });
276};
277
278// �� Usage:
279
280/// Create a config
281const config = {
282  name: 'my app',
283};
284
285/// Use the plugin
286export default withCustomProductName(config, 'new_name');
287```
288
289<Collapsible summary="Using SDK 46 or lower?">
290
291For SDK 46 and lower, import the `@expo/config-plugins` package directly. This is installed automatically by the `expo` package, but not re-exported as it is in SDK 47 and higher.
292
293```js
294const { ConfigPlugin, withXcodeProject } = require('@expo/config-plugins');
295```
296
297</Collapsible>
298
299### Experimental functionality
300
301Some parts of the mod system aren't fully fleshed out, these parts use `withDangerousMod` to read/write data without a base mod.
302These methods essentially act as their own base mod and cannot be extended.
303Icons, for example, currently use the dangerous mod to perform a single generation step with no ability to customize the results.
304
305```js my-config-plugin.js
306export const withIcons = config => {
307  return withDangerousMod(config, [
308    'ios',
309    async config => {
310      // No modifications are made to the config
311      await setIconsAsync(config, config.modRequest.projectRoot);
312      return config;
313    },
314  ]);
315};
316```
317
318Be careful using `withDangerousMod` as it is subject to change in the future.
319The order with which it gets executed is not reliable either.
320Currently, dangerous mods run first before all other modifiers, this is because we use dangerous mods internally for large file system refactoring like when the package name changes.
321
322## Plugin module resolution
323
324The strings passed to the `plugins` array can be resolved in a few different ways.
325
326> Any resolution pattern that isn't specified below is unexpected behavior, and subject to breaking changes.
327
328### Project file
329
330You can quickly create a plugin in your project and use it in your config.
331
332- <YesIcon /> `'./my-config-plugin'`
333
334- <NoIcon /> `'./my-config-plugin.js'`
335
336```
337╭── app.config.js ➡️ Expo Config
338╰── my-config-plugin.js ➡️ ✅ `module.exports = (config) => config`
339```
340
341### app.plugin.js
342
343Sometimes you want your package to export React components and also support a plugin. To do this, multiple entry points need to be used because the transpilation (Babel preset) may be different.
344If an **app.plugin.js** file is present in the root of a Node module's folder, it'll be used instead of the package's `main` file.
345
346- <YesIcon /> `'expo-splash-screen'`
347
348- <NoIcon /> `'expo-splash-screen/app.plugin.js'`
349
350```
351╭── app.config.js ➡️ Expo Config
352╰── node_modules/expo-splash-screen/ ➡️ Module installed from NPM (works with Yarn workspaces as well).
353    ├── package.json ➡️ The `main` file will be used if **app.plugin.js** doesn't exist.
354    ├── app.plugin.js ➡️ ✅ `module.exports = (config) => config` -- must export a function.
355    ╰── build/index.js ➡️ ❌ Ignored because **app.plugin.js** exists. This could be used with `expo-splash-screen/build/index.js`
356```
357
358### Node module default file
359
360A config plugin in a node module (without an **app.plugin.js**) will use the `main` file defined in the **package.json**.
361
362- <YesIcon /> `'expo-splash-screen'`
363
364- <NoIcon /> `'expo-splash-screen/build/index'`
365
366```
367╭── app.config.js ➡️ Expo Config
368╰── node_modules/expo-splash-screen/ ➡️ Module installed from NPM (works with Yarn workspaces as well).
369    ├── package.json ➡️ The `main` file points to **build/index.js**
370    ╰── build/index.js ➡️  ✅ Node resolves to this module.
371```
372
373### Project folder
374
375- <YesIcon /> `'./my-config-plugin'`
376
377- <NoIcon /> `'./my-config-plugin.js'`
378
379This is different to how Node modules work because **app.plugin.js** won't be resolved by default in a directory. You'll have to manually specify `./my-config-plugin/app.plugin.js` to use it, otherwise **index.js** in the directory will be used.
380
381```
382╭── app.config.js ➡️ Expo Config
383╰── my-config-plugin/ ➡️ Folder containing plugin code
384    ╰── index.js ➡️ ✅ By default, Node resolves a folder's index.js file as the main file.
385```
386
387### Module internals
388
389If a file inside a Node module is specified, then the module's root **app.plugin.js** resolution will be skipped. This is referred to as "reaching inside a package" and is considered **bad form**.
390We support this to make testing, and plugin authoring easier, but we don't expect library authors to expose their plugins like this as a public API.
391
392- <NoIcon /> `'expo-splash-screen/build/index.js'`
393
394- <NoIcon /> `'expo-splash-screen/build'`
395
396```
397╭── app.config.js ➡️ Expo Config
398╰── node_modules/expo-splash-screen/ ➡️ Module installed from npm (works with Yarn workspaces as well).
399    ├── package.json ➡️ The `main` file will be used if **app.plugin.js** doesn't exist.
400    ├── app.plugin.js ➡️ ❌ Ignored because the reference reaches into the package internals.
401    ╰── build/index.js ➡️ ✅ `module.exports = (config) => config`
402```
403
404### Raw functions
405
406You can also just pass in a config plugin.
407
408```js app.config.js
409const withCustom = (config, props) => config;
410
411const config = {
412  plugins: [
413    [
414      withCustom,
415      {
416        /* props */
417      },
418    ],
419    // Without props
420    withCustom,
421  ],
422};
423```
424
425One caveat to using functions instead of strings is that serialization will replace the function with the function's name. This keeps **manifests** (kinda like the **index.html** for your app) working as expected.
426
427Here is what the serialized config would look like:
428
429```json
430{
431  "plugins": [["withCustom", {}], "withCustom"]
432}
433```
434
435## Why app.plugin.js for plugins
436
437Config resolution searches for a **app.plugin.js** first when a Node module name is provided.
438This is because Node environments are often different to iOS, Android, or web JS environments and therefore require different transpilation presets (ex: `module.exports` instead of `import/export`).
439
440Because of this reasoning, the root of a Node module is searched instead of right next to the **index.js**.
441Imagine you had a TypeScript Node module where the transpiled main file was located at **build/index.js**,
442if Expo config plugin resolution searched for **build/app.plugin.js** you'd lose the ability to transpile the file differently.
443
444## Next step
445
446<BoxLink
447  title="Development and debugging"
448  description="Learn about development best practices and debugging techniques for Expo config plugins."
449  href="/config-plugins/development-and-debugging"
450/>
451
452[xml2js]: https://www.npmjs.com/package/xml2js
453[expo-plist]: https://www.npmjs.com/package/@expo/plist
454