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