Skip to main content

What is Metro?

metro finds Discord’s internal modules for you. It walks the module registry and returns the module that matches the description you give it. Discord’s mobile app is a React Native bundle: thousands of Metro modules packed into one file. Nothing is global. Every store, component, and helper sits behind an opaque numeric module id, so before you can change a behavior you have to find the module that owns it. Other parts of the API depend on Metro too. assets, i18n, the common handles, flux stores, and your own addons all use it to locate modules.
Learn this module first. The search patterns on this page come up throughout the rest of the API.

Finding modules

Each findBy* helper matches a module by one kind of identifying trait, such as its properties, its name, or its prototype. You rarely know a module’s id, but you usually know one of these.
Match a module by the properties it exposes. This is the most common search.
All search helpers:
On mobile, use findByName for components, since most are identified by their name. findByDisplayName covers components that set a displayName, which is less common on mobile than on desktop.
Mobile has no search by source string. Desktop clients can match a module by string literals in its source, which is the usual way to find a function component there. The mobile bundle doesn’t expose module source in the same way, so this technique isn’t available. If a component has no props, prototype, or name to match on, reach it through its parent instead. See Reaching into a render.

Filters

A filter is a predicate that returns true for the module you want. Every findBy* helper is a shortcut for a filter. Filters live on metro.filters. If the built-in helpers don’t fit the shape you need, build your own filter and pass it to find (or findLazy).
Use find with a raw filter only when no findBy* helper can express the match, for example a combination of conditions. Otherwise use the helpers: they’re shorter and cache better.

Lazy resolution & caching

Metro keeps startup fast in two ways: it caches search results, and it can defer a search until you use the result. Every filter has a cache key, so Metro remembers a successful search. The next search for the same shape resolves from the cache instead of walking the registry. To defer a search, pass { lazy: true }. You get a proxy back, and the search runs the first time you access it. A module referenced at the top of a file but used only inside a rarely called function costs nothing until that function runs.
Use lazy resolution for module handles declared at module scope. This is how Unbound references dozens of modules without paying for all of them at launch.

Caching custom filters

The findBy* helpers build their cache key by serializing their arguments, so their results are remembered across cold starts. A filter function you write yourself has no key, so Metro can’t cache it. Every cold start re-runs your predicate against the whole registry, which makes it the one kind of search that stays slow. To fix this, wrap your filter in createCacheable and pass a second function that serializes the filter’s arguments into a key. Metro then caches the result under that key, the same way it does for a built-in filter.
The key must fully describe the filter’s inputs. If two searches should return different modules, they need different keys. Otherwise they collide in the cache.

Common modules & stores

Metro pre-resolves the modules most addons need: React, React Native, the dispatcher, constants, and the main flux stores. Use these instead of searching for them again.
Framework and app-level handles you would otherwise look up with findByProps again and again: React, ReactNative, Dispatcher, Constants, Clipboard, Theme, Flux, Assets, i18n, and more.
Pre-resolved flux stores such as Users, Guilds, and Theme.
Action-creator modules such as Messages, Linking, and Profiles, used to trigger actions in the app.