A config plugin is how a native setting gets into your app without anyone opening the ios folder.
An Expo config plugin is a JavaScript function named in the plugins array of your app config. It runs while the native projects are being generated, and it edits them for you. That is the whole idea. In a project whose android and ios folders are produced from a template, a change typed in by hand cannot be relied on to survive, so the change has to live somewhere that gets applied every time the folders are built.
This page is written for someone who was handed a React Native and Expo project and did not type it. A library tells you to add a plugin. A build fails and someone says it is a plugin problem. Three things settle most of it: what a plugin is, whether your project even runs the step that applies one, and what a plugin is allowed to touch. Every fact below comes from Expo's own documentation, read on 17 October 2026, with the date Expo prints on each page quoted next to it. If the framework itself is new to you, start with what React Native is.
Start hereThe short version
Three facts settle most of what people get wrong about config plugins.
The first is timing. A plugin only edits native files during one phase of one command, the syncing phase of prebuild. Add a plugin to app.json and nothing on disk changes until the native projects are generated again.
The second is reach. A plugin can edit the files Expo has a modifier for, which on 17 October 2026 was a documented list of 18 files, and no others without extra work. It is not a general licence to rewrite the project.
The third is the one that wastes an afternoon. If your repository has android and ios folders committed in it, the hosted build service does not run prebuild at all, so a plugin you added sits there doing nothing. Expo states both halves of that in writing, and they are quoted below.
What is an Expo config plugin, exactly?
Expo defines it as a top-level custom configuration point that is not built into the app config. You reference it in the plugins property of app.json or app.config.js, and it is made of one or more plugin functions, written in JavaScript, that run during prebuild. The naming convention is a verb-like prefix: withMyPlugin, withFacebook, withWhateverYouAreAdding. If you see a function in the project whose name starts with that word, this is probably what it is.
Underneath there are three more layers, and knowing their names makes error messages readable. The plugin calls plugin functions, one per platform in most cases. Those call mod plugin functions, which come from Expo's own config-plugins package and are the documented safe way to touch a native file. Those wrap mods, short for modifiers, which are the things that actually open AndroidManifest.xml or Info.plist. Expo tells plugin authors to use the mod plugin functions and to leave the raw mods alone.
Using one is two lines of configuration. In the plugins array, a plain string is the package or file name on its own. An array of two entries is the name plus an options object, which is how a library takes settings from you. Order in the array is the order they run, and each plugin receives the config the previous one returned. So a plugin added later can quietly overwrite one added earlier, which is worth remembering when two of them touch the same file.
One characteristic matters more than the rest. Plugins are evaluated every time the app config is evaluated, but mods run only during the syncing phase of prebuild. Expo draws the conclusion itself: changes a plugin makes to the app config should sit outside a mod, so they still happen when prebuild is not running. Read that the other way round and you have the honest limit of the whole system. A plugin is a build-time tool. It cannot change a running app.
Expo, introduction to config plugins, page dated 17 September 2026, read 17 October 2026
Do you have to care, if you did not write the project?
One check answers it. Open the repository and look at the top level for folders named android and ios. That single fact decides how every native setting in your app gets there, and it decides whether a plugin does anything at all.
If they are not there, the project generates them when it needs them, and app config plus plugins is the only route a native setting has. That is not a gap in your repository. Expo says those two folders are added to the ignore file automatically when a project is created, so most projects of this kind are missing them on purpose, and the hosted build service generates them before it compiles.
If they are there, two sentences from Expo matter. The app config reference says, under the plugins property, that plugins which add modifications can only be used with prebuilding and managed EAS Build. The native generation page says that for a project which has android and ios directories, the build service will not run prebuild so as not to overwrite the changes you have made in them. Put those together and the consequence is blunt. In that repository a plugin you add is inert until somebody runs prebuild by hand, and the run that finally activates it can overwrite the native folders it was added to.
The cost of ignoring all this is specific and common. You open the iOS project, add a key to Info.plist, build locally, and it works. Months later someone regenerates the native folders and your key is gone, along with any reason anyone could find for why it was there. Expo writes this one down too: modify the generated directories by hand and you risk losing the changes the next time prebuild runs with the clean option. The repository you own is the record of your app. Anything true about the app that is not in it will eventually stop being true, which is the same argument as who owns AI generated code, seen from the build side.
When do you need a plugin, and when does app.json already cover it?
Expo frames plugins as the way to configure a native project beyond what the default app config properties can do. The word beyond is doing real work there, because the ordinary properties cover more than people assume. The iOS one is a dictionary called ios.infoPlist that takes any keys you want added to the native Info.plist, applied before all other Expo specific configuration, with no validation of what you put in it. The Android one, android.permissions, is described as a list of permissions added to AndroidManifest.xml during prebuild. Between them that is a large share of the reasons people reach for a plugin.
The second thing that removes the work is that libraries ship their own. An Expo library that needs native configuration includes a plugin in its package and documents it in its API reference. You install the package, add its name to the plugins array, and pass options in the array form if it takes any. The camera library is the example Expo uses, with the permission wording as an option. So the instruction add a config plugin, in a library README, almost never means write one.
Build settings have a ready-made plugin as well. The expo-build-properties package is an Expo config plugin whose only job is writing the two native build files, android/gradle.properties and ios/Podfile.properties.json. Its documented options include minSdkVersion, targetSdkVersion, compileSdkVersion and kotlinVersion on Android, and useFrameworks, forceStaticLinking and extraPods on iOS. Expo attaches a warning to it that applies to the whole subject: it cannot be used in a project that does not run prebuild.
Before any of that, check whether the package needs native configuration at all. Expo is explicit that many packages do not require any beyond what autolinking already handles, and so no config plugin is required for them. The ones that catch people are the packages which do need setup after installation and have not adopted the system, which Expo also admits: not all packages support its prebuild yet. For popular ones it keeps a separate repository of out-of-tree plugins, which is worth searching before anybody writes code.
What is left is when you write one. Expo lists those cases itself, under the tradeoffs of generating native projects. Changing a file such as gradle.properties means writing a plugin. Adding a native file quickly, to try something, is slower than it was. And a library with native setup steps and nobody to write the plugin is work somebody has to do, which is you or whoever you hire.
Check yours
Do you need to write a plugin for this?
Pick the change you are trying to make. Most of them already have a documented route that is not writing code.
Nothing picked. Worth saying before you pick anything: of the nine changes above, four are handled without writing a single line, and two more are handled by a plugin someone else already wrote.
What is a config plugin allowed to change?
The files Expo has a modifier for, and the list is finite. On 17 October 2026 the documentation listed 18 of them, 11 for Android and 7 for iOS, each one naming the native file it rewrites and how it hands that file to your function. Six of the 18 are flagged dangerous in Expo's own table. The full list is below, because this is the honest answer to what a plugin can and cannot do, and it is shorter than most people expect.
The split in that table is about how the file is handed over. The structured ones arrive parsed. An XML manifest comes in as an object, a plist comes in as an object, the Podfile properties come in as JSON. You change a value and hand it back, and the shape protects you. The six dangerous ones arrive as one long string, because there is no safe general way to parse Java, Objective-C or Gradle source for this purpose. The edit becomes a search and replace in somebody else's code.
That is why Expo calls them dangerous rather than advanced. Its note says these modifications rely on regular expressions to change application code, which may cause the build to break, that they are difficult to version, and that they should be used sparingly. Its recommendation is to use application code to change application code, meaning a native module, instead of a pattern match. Expo also says elsewhere that these modifiers are rarely safe to run twice, and are especially prone to breaking when the native templates change under them, so they want testing with every SDK release. If your project has one, that is a standing maintenance cost and somebody should know it exists.
Two further limits. The list can be extended with a custom mod for a file Expo does not cover, and its own example is the Google services plist, so the finite list is a floor rather than a ceiling. And mods are left out of the manifest and cannot be read at runtime through the updates API. A plugin decides what gets built. It cannot be consulted by the app afterwards.
Every native file a plugin can edit, as documented
The eighteen documented modifiers, with the file each one rewrites and whether Expo flags it dangerous. Read 17 October 2026. The dangerous flag is drawn as an icon in Expo's table, so it disappears if you copy that table as text, which is worth knowing if you are checking this yourself.
| Platform | Mod plugin | File it rewrites | Flagged dangerous |
|---|---|---|---|
| Android | withAndroidManifest | AndroidManifest.xml | No |
| Android | withStringsXml | res/values/strings.xml | No |
| Android | withAndroidColors | res/values/colors.xml | No |
| Android | withAndroidColorsNight | res/values-night/colors.xml | No |
| Android | withAndroidStyles | res/values/styles.xml | No |
| Android | withGradleProperties | android/gradle.properties | No |
| Android | withMainActivity | MainActivity, as a string | Yes |
| Android | withMainApplication | MainApplication, as a string | Yes |
| Android | withAppBuildGradle | android/app/build.gradle | Yes |
| Android | withProjectBuildGradle | android/build.gradle | Yes |
| Android | withSettingsGradle | android/settings.gradle | Yes |
| iOS | withInfoPlist | Info.plist | No |
| iOS | withEntitlementsPlist | The entitlements file | No |
| iOS | withExpoPlist | Expo.plist | No |
| iOS | withXcodeProject | The Xcode project file | No |
| iOS | withPodfile | ios/Podfile, as a string | No |
| iOS | withPodfileProperties | ios/Podfile.properties.json | No |
| iOS | withAppDelegate | AppDelegate, as a string | Yes |
What goes wrong, and how to look instead of guessing
The failure people hit first is a plugin that is not safe to run twice. Expo explains the mechanism. Running prebuild without the clean option layers changes on top of the files already there, which is faster but may not produce the same result, because some plugins are not idempotent, and a project using several of the dangerous modifiers can behave unexpectedly. Its conclusion is that the clean option, which deletes the native folders before generating them, is the safest way to run the command and is generally recommended. The trap in that advice is obvious once stated: the safest option is also the one that throws away anything anybody typed into those folders.
The second failure is upgrade day. A plugin that pattern-matches source code is matching source code that Expo owns and changes between releases. Expo asks for dangerous mods to be tested with each SDK release for exactly that reason. Its patch-project tool, which turns manual native edits into patch files and reapplies them at prebuild, carries the same warning in stronger words: those patches can be dangerous, can collide with a plugin editing the same file, and can go stale after an SDK upgrade because the templates move. None of this is a reason to avoid plugins. It is a reason to know which ones you have.
So look. Expo documents a config command with a type flag that answers both questions without generating a thing. The prebuild type prints the result of your plugins with the modifiers left unevaluated, so no code is written. The introspect type goes further and shows the evaluated results, for a documented subset: the Android manifest, gradle properties, strings, colors, night colors and styles, and on iOS Info.plist, the entitlements, Expo.plist and the Podfile properties. The Xcode project is left out because it usually needs changes on disk. The same output carries a plugin history listing every plugin by name and version, where the word unversioned means the installed package shipped no plugin and the command fell back to a legacy one, which Expo suggests avoiding. Setting the debug variable before prebuild prints which modifiers ran, in order.
Spending an hour on those commands is the cheapest part of shipping an app, and it is a different kind of cost from the ones in what it costs to publish an app. Store fees and certificates are predictable and you can plan for them. A native change nobody wrote down is the kind of cost that arrives on the day you can least afford it, usually the day before a release, wearing the costume of an unexplained build failure.
Where this lands if a builder generated your app
It lands in exactly the same place, because the project is an ordinary React Native and Expo project and the rules are the framework's rather than the builder's. Newly is an AI app builder. You describe an app and it writes a real React Native and Expo project that you own, with two-way GitHub sync, so the repository on your account is the thing these commands run against. It uploads iOS builds to TestFlight, publishes to Google Play internal testing, and builds a standalone Android APK. It is $25 a month, with no free plan.
The one thing it does not do is press submit for you on the App Store, and the reason is Apple's own rule rather than a limitation. App Store Review Guideline 4.2.6 rejects apps made from a commercialised template or app generation service unless the provider of the content submits them directly. You are the content owner, so you submit. That is also where plugins meet the store: a permission string, an entitlement and a background mode are all plugin or app config territory, and all three are things review asks about. The sequence is covered in publishing to the App Store.
The useful habit, whoever or whatever wrote the first version, is to treat the plugins array as documentation. It is a short list in a file you can read, and it is the only place that explains why the native project looks the way it does. Before you let anyone, human or otherwise, edit a native folder by hand, ask whether the change could live in that array instead. If it can, it will still be there next year.
Questions people ask about Expo config plugins
It is a JavaScript function named in the plugins array of your app config, which edits the generated native projects while they are being generated. Expo calls it a top-level custom configuration point that is not built into the app config, and says a plugin is made up of one or more plugin functions that run during prebuild. By convention the function is named with something, such as withMyPlugin. The reason it exists is that the android and ios directories in this kind of project are regenerated from a template, so a change typed into them by hand cannot be relied on to survive.
Describe the app, then keep the native changes in one file
Read your plugins array before you read anything in the ios folder. It is shorter, it is the source of truth, and it is the only version of your native setup that survives the next time those folders are built from scratch.
Start building