Expo Application Services
API Reference

Metro bundler


You can customize the Metro bundler by creating a metro.config.js file at the root of your project. This file should export a Metro configuration that extends expo/metro-config. You should import expo/metro-config instead of @expo/metro-config to ensure version consistency.
Run the following to generate the template file:
→ npx expo customize metro.config.js
The metro.config.js file looks something like this:
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);

module.exports = config;
To learn more, see metro.config.js documentation.


Metro resolves files as either source code or assets. Source code is JavaScript, TypeScript, JSON, and other files used by your application. Asset are images, fonts, and other files that should not be transformed by Metro. To accommodate large-scale codebases, Metro requires all extensions for both source code and assets to be explicitly defined before starting the bundler. This is done by adding the resolver.sourceExts and resolver.assetExts options to the Metro configuration. By default, the following extensions are included:
  • resolver.assetExts
  • resolver.sourceExts

Adding more file extensions to assetExts

The most common customization is to include extra asset extensions to Metro.
In the metro.config.js file, add the file extension (without a leading .) to resolver.assetExts array:
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);

  // Adds support for `.db` files for SQLite databases

module.exports = config;


Source code is optimized for readability and debugging. To optimize your application for performance, the source code is automatically minified when compiling and exporting. You can also minify your code during development with npx expo start --minify. This is sometimes useful for testing production optimizations.
By default, Metro uses uglify-es to minify code. According to this benchmark uglify generally produces the smallest bundles, and is nearly the slowest minifier. Here are some alternative minifiers you can use with Metro.

Minification: esbuild

You can use esbuild to minify exponentially faster than uglify-es and terser. Just follow this guide: metro-minify-esbuild usage.

Minification: Terser

You can use terser instead of uglify-es to mangle and compress your project.
First, install Terser in your project by running the following command:
→ yarn add --dev metro-minify-terser
Now set Terser as a minifier with transformer.minifierPath, and pass in terser options via transformer.minifierConfig.
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);

config.transformer.minifierPath = 'metro-minify-terser';
config.transformer.minifierConfig = {
  // Terser options...

module.exports = config;

Minification: Uglify

By default, Metro uses uglify-es to minify and compress your code. You can customize uglify by passing options to transformer.minifierConfig. For example, if you want to remove all console.log() methods from your app in production, you can do the following:
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);

// Remove all console logs in production...
config.transformer.minifierConfig.compress.drop_console = true;

module.exports = config;
Here are all of the default Uglify options applied in Metro bundler.

Source map exploration

A useful way to debug your source code is by exploring the source maps with react-native-bundle-visualizer. Install it with yarn add -D react-native-bundle-visualizer, then run npx react-native-bundle-visualizer.
This will show you an interactive breakdown of what makes up your JavaScript bundle. Using this, you can find large packages that you may not have intended to bundle in your project. The smaller the bundle, the faster your app will start.

Web Support

Metro web support is an experimental feature and only works with the local Expo CLI and Expo SDK 46 or newer.
By default, Expo CLI uses webpack as the bundler on web platforms, this is because Metro historically did not support web. Using different bundlers across platforms leads to some critical divergence in how your app works across platforms. Features like Fast Refresh which work on native don't work on web, and important production functionality like assets are treated differently across bundlers.
By utilizing Metro across all platforms you can have a more universal development experience. You also get to utilize shared cached chunks across platforms meaning faster iteration speed when working across platforms. Project upgrades can also be easier since there are less dependencies (webpack, webpack-dev-server) you need to update between versions.
All Metro web features should be universal (bundling, static files, assets) making the DX easier to understand and faster across the app development process.
Learn once, bundle everywhere!

Expo webpack vs. Expo Metro

Universal Expo Metro is designed to be fully universal, meaning any web bundling features should also work on native too. Because of this we make some breaking changes between the two bundler implementations, carefully check the difference if you're moving from webpack to Metro.
Start commandnpx expo startnpx expo start
Bundle commandnpx expo exportnpx expo export:web
Output folderdist/web-build/
Static folderpublic/web/
Config filemetro.config.jswebpack.config.js
Default config@expo/metro-config@expo/webpack-config
Fast RefreshStatus-waiting-icon (coming soon)Status-failed-icon
Tree ShakingStatus-failed-iconStatus-success-icon
CSS HandlingStatus-failed-iconStatus-success-icon
Asset ManifestsStatus-failed-iconStatus-success-icon
Bundle SplittingStatus-waiting-icon (pending native)Status-success-icon
Note that aliases, resolution, and other bundler features are now universal across platforms as well!

Adding Web support to Metro

To enable Metro web support, be sure to use Expo SDK 46 or newer, then modify your Expo config in app.json, or app.config.js to enable the feature using the expo.web.bundler field:
  "expo": {
    "web": {
      "bundler": "metro"


To start the development server run the following command:
→ npx expo start --web
Or start normally and press W in the Terminal UI.


TL;DR: Use npx expo export instead of npx expo export:web or expo build:web.
You can bundle the project for hosting just like you would for native:
→ npx expo export
You should see something like this:
app $ npx expo export
Starting Metro Bundler
iOS ./index.tsx ░░░░░░░░░░░░░░░░  4.0% (  8/132)
Android ./index.tsx ░░░░░░░░░░░░░░░░  0.5% (  3/129)
Web ./index.tsx ░░░░░░░░░░░░░░░░  4.0% ( 5/5
You can skip bundling for all platforms by using the --platform flag:
→ npx expo export --platform web
Your output will be found in the dist folder.
The output can be tested locally by using the Serve CLI.
→ npx serve dist
You can deploy the website using any popular web host, follow any of the guides in publishing websites, just substitute the web-build/ folder for the dist/ folder.

Static Files

Expo's Metro implementation supports hosting static files from the dev server by putting them in the root public/ directory, this is akin to many other web frameworks. In Expo webpack, we default to using the web/ directory.
When exporting with npx expo export, we copy the contents of the public/ directory into the dist/ directory, meaning your app can expect to fetch these assets relative to the host URL.
The most common example of this is the public/favicon.ico which is used by websites to render the tab icon.
You can overwrite the default index.html in Metro web by creating a public/index.html file in your project.
In the future, this will work universally across platforms with EAS Update hosting. Currently the feature is web-only based on the static host used for the native app, for example, the legacy Expo service updates does not support this feature.
  • Message-iconAsk a question on the forums
  • Edit-iconEdit this page

Was this doc helpful?