In a real app
In app.json, a plugin is declared by package name or local path, with optional options: ["expo-camera", { "cameraPermission": "…" }]. A very common one is expo-build-properties, which sets minSdkVersion, deploymentTarget, or useFrameworks without touching native files. A local plugin usually lives in plugins/withSomething.ts and is registered by relative path.
Typical mistakes: a plugin declared for an uninstalled package (prebuild stops with an error, by design), a mod that rewrites a whole file instead of modifying it (later mods lose their changes), and withDangerousMod running regexes over the Podfile or MainActivity.kt that break as soon as the template changes with a new SDK. A plugin is also evaluated every time the config is read, not only at prebuild; slow side effects (network reads, shelling out) slow down expo start and EAS.
To confirm a plugin does what you think: npx expo prebuild --clean followed by git diff on a project where the native folders are temporarily committed, or npx expo config --type introspect to see the mods’ output without generating. Plugins are tested with expo-module-scripts and snapshots of native files.
Related terms
Prebuild (Continuous Native Generation)
The Expo command that generates the android and ios folders from app.json and config plugins instead of maintaining them by hand.
Expo Modules API
Expo’s API for writing native modules and views in Swift and Kotlin with one shared DSL, with no Objective-C or bridge boilerplate.
Expo
The open-source framework React Native recommends: a native module SDK, a CLI, prebuild, Expo Router, and the EAS cloud services.
EAS Build
Expo’s hosted build service: profiles in eas.json, credentials management, and macOS and Linux runners for iOS and Android.