In an Expo project, how do you see what your config plugin's mods will produce without generating the ios and android folders?
answer
- read, don't write
- npx expo config --type …
- _internal.modResults per mod
- safe mods only; dangerous skipped
- EXPO_DEBUG=1 prints the plugin stack
basics
~10 sRun npx expo config --type introspect: it evaluates the introspectable mods, such as Info.plist, entitlements and the Android manifest, in memory and prints their results under _internal.modResults without writing any native files.
solid answer
~40 sUse **introspection**: `npx expo config --type introspect`. It compiles the mods with introspection enabled, so the base mods read existing files or templates, run your callbacks, and save each result to `_internal.modResults.<platform>.<mod>` instead of writing to disk. Only safe mods take part: the Android manifest, strings, colors, styles and gradle properties, and on iOS Info.plist, entitlements, Expo.plist and Podfile properties. Everything else is removed, including dangerous mods, the Xcode project mod and the string-based mods, because they need real file-system changes or raw text. A mod can check `config.modRequest.introspect` to avoid side effects. Two neighbours help: `npx expo config --type prebuild` prints the config with plugins applied and mods unevaluated, including `_internal.pluginHistory`, and `EXPO_DEBUG=1 npx expo prebuild` logs which plugin registered each mod. EAS Build uses the same introspection to read final iOS entitlements.
code
bash · 8 lines# Evaluate safe mods in memory; nothing is written to ios/ or android/
npx expo config --type introspect
# Plugins applied, mods not evaluated; shows _internal.pluginHistory
npx expo config --type prebuild
# Real prebuild with the plugin stack logged for every mod
EXPO_DEBUG=1 npx expo prebuild --no-installgo deeper
Recall that npx expo config has an introspect type that previews what mods produce without generating native folders.
Explain which mods are introspectable, where the results appear, and how --type prebuild and EXPO_DEBUG=1 complement it.
Show you write mods that respect modRequest.introspect and keep file creation in dangerous mods, so previews and EAS capability sync stay accurate.
Use introspectability as a review criterion for plugins: an effect that cannot be previewed is one the team and EAS tooling cannot verify.
## The problem introspection solves A config plugin's real output is a set of edited native files, and the obvious way to see them is to run `npx expo prebuild` and open `ios/` and `android/`. That is slow, it may install CocoaPods, and in a Continuous Native Generation project those folders are usually not committed at all. **Introspection** is Expo's way to evaluate mods and read their results without generating anything. ## How it works `npx expo config --type introspect` does three things: 1. Builds the prebuild config, applying every plugin in the project, so all mods are registered. 2. Compiles the mods with `introspect: true`. The compiler first **removes every mod that is not introspective**, which drops dangerous mods, the Xcode project mod and the string-based file mods. 3. Runs the remaining mods through **introspection base mods**, which read the current file (or a template when it does not exist), run your callbacks, and store the result in the config instead of writing it. The results appear under `_internal.modResults`, keyed by platform and mod, for example `_internal.modResults.ios.infoPlist` and `_internal.modResults.android.manifest`. ## Which mods take part | Platform | Introspectable mods | |---|---| | Android | `manifest`, `gradleProperties`, `strings`, `colors`, `colorsNight`, `styles` | | iOS | `infoPlist`, `entitlements`, `expoPlist`, `podfileProperties` | | Excluded | everything else: `dangerous` mods, `ios.xcodeproj` (which often needs file-system changes) and the string-based mods such as the Podfile and Gradle files | These are the **static-file** mods: JSON, XML, plist and properties files the compiler can parse and rebuild in memory. It is one more argument for preferring typed mod plugins over text edits: their effect is inspectable before any build. ## Writing mods that behave under introspection - A mod receives `config.modRequest.introspect`; when it is `true`, the mod must not write, create or delete files. - Expo's guidance is to **generate, move and delete files only in dangerous mods**; doing it inside a typed mod breaks introspection, because that mod would still run and touch disk. - Keep mods free of network calls and interactive prompts; introspection may run them often, for example from editor tooling. ## The rest of the debugging toolkit | Tool | What it shows | |---|---| | `npx expo config --type prebuild` | The config after plugins, with mods registered but not evaluated; includes `_internal.pluginHistory` | | `EXPO_DEBUG=1 npx expo prebuild` | For each mod, the stack of plugins that registered it, in the order they were invoked | | `EXPO_CONFIG_PLUGIN_VERBOSE_ERRORS=1` | Plugin resolution errors that are hidden by default | | `npx expo prebuild --clean` | A from-scratch generation, the reference result to diff against | The Expo Tools extension for VS Code also offers a live "preview modifier" view built on introspection. ## What introspection output typically reveals - **A value you did not set.** The Info.plist and entitlements base mods merge the existing file with `ios.infoPlist` or `ios.entitlements` from app config before your callback runs, so a key can come from either source. - **An entry missing entirely.** Either the plugin was never applied (check `_internal.pluginHistory` in the `--type prebuild` output and the `plugins` list) or the change was made in a dangerous mod, which introspection skips. - **A crash only in introspection.** A mod that reads some other native file from disk by itself fails here, because nothing has been generated; the base mods cope by falling back to a template, or to an empty path for Info.plist and entitlements. - **A duplicated array item.** If the preview already shows two copies, the plugin appends without checking, and a layered prebuild would do the same on disk. ## Where introspection is used for real It is not only a debugging aid. EAS Build introspects a managed project's config to learn the **final iOS entitlements** before it builds, then enables or disables the matching capabilities on the Apple Developer Console so that the provisioning profile matches. If your plugin adds an entitlement through `withEntitlementsPlist`, that path sees it; if you wrote the `.entitlements` file from a dangerous mod, it does not. ## A practical routine - Add or change a plugin. - Run `npx expo config --type introspect` and check the relevant `_internal.modResults` entry. - Run `EXPO_DEBUG=1 npx expo prebuild --no-install` when a value is wrong, to see which plugin touched the mod last. - Diff a `--clean` prebuild against a layered one to catch non-idempotent mods.
- Why does EAS Build care whether an Expo plugin adds an entitlement with withEntitlementsPlist or with a dangerous mod?EAS Build reads the final entitlements through introspection to sync Apple capabilities before building. The entitlements mod is introspectable, so its change is seen. A dangerous mod is dropped in introspection, so an entitlement written that way is missed and the capability may not be enabled, failing signing.
- What should an Expo config plugin mod do when config.modRequest.introspect is true?Compute and return `modResults` as usual but perform no file-system side effects: no writing, creating or deleting files. The introspection base mod stores the result in `_internal.modResults` instead of writing it, and any extra I/O in your mod would break that guarantee.
saying these in an interview costs you the question
- Introspection runs dangerous mods too, just in a temporary folder.
- npx expo config --type prebuild shows the evaluated Info.plist contents.
- The only way to check a plugin is to run prebuild and open the files.
- Creating new native files inside a typed mod is fine for introspection.