In an Expo app config, why does the order of the plugins array matter when two plugins edit the same native file?
answer
- two phases, two orders
- functions: array order
- mods wrap earlier mods
- later plugin's mod runs first
- EXPO_DEBUG=1 shows the stack
basics
~20 sPlugin functions run in array order, but each mod for a file wraps the earlier ones, so at prebuild the later plugin's edit runs first and the earlier plugin's edit runs last. For the same manifest key, the plugin listed earlier wins.
solid answer
~40 sOrder works differently in the two phases. The plugin **functions** are chained with `withPlugins` in array order, so for values set directly on the config object the later entry wins. **Mods** are chained per file: each new mod wraps those registered before it, and at prebuild the file is read once, passed to the outermost mod first, then inward. So the plugin listed **later** edits first and the one listed **earlier** edits last; if both set the same manifest attribute, the earlier plugin's value is written. Expo's own `expo-build-properties` source notes that the later one runs first. Dangerous mods also run before typed ones on each platform. I debug with `EXPO_DEBUG=1 npx expo prebuild` and prefer one owner per setting.
code
typescript · 14 lines// app.config.ts
import type { ExpoConfig } from 'expo/config';
const config: ExpoConfig = {
name: 'Courier',
slug: 'courier',
plugins: [
// Listed first on purpose: its manifest mod runs last, so its maps key wins.
'./plugins/withMapsKey.js',
'maps-library',
],
};
export default config;go deeper
Recall that the plugins array is ordered and that two plugins touching the same native setting can conflict.
Explain why functions run in array order while mods for one file run in reverse, and which plugin wins a conflicting key.
Diagnose a conflict with EXPO_DEBUG and a clean prebuild, then fix ownership rather than relying on a fragile position in the array.
Set a policy of one owner per native setting and documented, load-bearing ordering, so plugin changes stay reviewable across a team.
## Two kinds of order The `plugins` array in an Expo app config is ordered, and the order matters in two different ways, because a config plugin works in two phases. **Phase 1: plugin functions, in array order.** When the app config is evaluated, Expo chains the entries with `withPlugins`, which applies them one after another. The output of each plugin is the input of the next. If two plugins set the same **config value** directly, for example both change `config.android.permissions`, the **later** plugin sees the earlier plugin's result and has the final word. **Phase 2: mods, chained per file.** Most plugins do their real work in **mods**, edits to a specific native file such as the Android manifest or `Info.plist`, registered through helpers like `withAndroidManifest`. Each new mod for a file **wraps** the mods registered before it. During prebuild, Expo reads the file, then calls the outermost mod, which makes its edit and hands the result to the one it wrapped, and so on inward, before writing the file once. The consequence surprises people: 1. Plugin A is listed first and registers a manifest mod. 2. Plugin B is listed second and registers a manifest mod that wraps A's. 3. At prebuild, **B's edit runs first**, then A's. 4. If both set the same attribute, **A's value is written last and wins**. Expo's own `expo-build-properties` source notes this directly: "the later one would run first". ## A delivery-app example A delivery app adds a maps library whose plugin writes its API key into a manifest `meta-data` entry. The team also has a local plugin, `./plugins/withMapsKey.js`, that writes the same entry with a different key per build environment. | `plugins` order | Which key ends up in `AndroidManifest.xml` | |---|---| | maps library, then local plugin | The maps library's key | | local plugin, then maps library | The local plugin's key | For a manifest edit, the plugin that should win belongs **earlier** in the array, which is the opposite of what "later overrides earlier" intuition suggests. For a value the plugin sets on the config object itself, the later entry wins. Reading a plugin's source, or its documentation, tells you which kind of change it makes. ## Other ordering rules - **Dangerous mods run first.** For each platform, raw file-system mods run before the typed ones, and on iOS the Xcode project mod runs next because other mods read it. - **Provider mods come last.** The built-in "base" mods that read and write each file are added after every plugin; a mod added after the provider is an `INVALID_MOD_ORDER` error. - **Order across files does not interact.** A manifest mod and an `Info.plist` mod touch different files, so their relative order does not matter. ## Diagnosing an ordering problem 1. Run `EXPO_DEBUG=1 npx expo prebuild`. With debug enabled, prebuild logs the plugin stack for each mod, showing which mods ran and in what order. 2. Inspect the generated file after `npx expo prebuild --clean` rather than after an incremental prebuild, so leftovers from a previous run do not mislead you. 3. Check whether two plugins touch the same key at all; often the fix is to remove one of them rather than reorder. ## Practical guidance - Keep the array **short and intentional**; each entry is a native change someone must reason about. - Put a comment in `app.config.ts` (JSON cannot hold one) when an entry's position is load-bearing. - Prefer one owner per native setting: a single plugin that sets the maps key is easier to reason about than two that race. - When writing your own plugin, make its edits **idempotent** (safe to apply to an already-edited file), so re-running prebuild does not duplicate entries. This behaviour is from `@expo/config-plugins` in Expo SDK 57.
- Two plugins both push an entry into the same Android manifest list. What goes wrong, and what is the fix?Order decides which entry comes first, but both entries are written, so the manifest ends up with duplicates or conflicting meta-data. Reordering does not help. The fix is a single owner: remove one plugin's responsibility, or write your plugin to check whether the entry exists and replace it instead of appending.
- How can you see the order in which mods actually ran during prebuild?Run `EXPO_DEBUG=1 npx expo prebuild`. With debug enabled, `@expo/config-plugins` logs the plugin stack for each mod as it is invoked, for example which plugin chain led to the manifest mod. Combine it with `--clean` so the output reflects a fresh generation rather than edits layered on an old project.
saying these in an interview costs you the question
- Later plugins always override earlier ones, in every case
- Plugin order never matters because each plugin is isolated
- Mods run in the same order the plugins array lists them
- Reordering plugins fixes duplicate manifest entries
- Mods on different files still override each other by order