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