Skip to main content

Use headless mode

By default, MetaMask Connect renders its own QR code modal when connecting to MetaMask Mobile via MetaMask Wallet Protocol (MWP). Headless mode suppresses this built-in modal so you can render your own connection UI.

Use headless mode when you want to:

  • Display a custom-styled QR code that matches your dapp's design.
  • Show the connection URI in a different format (for example, a deeplink button instead of a QR code).
  • Integrate the connection flow into an existing modal or onboarding wizard.

Prerequisites

Follow Step 1 of the quickstart to install the EVM client.

Steps

1. Initialize the client with headless mode

Initialize an EVM client using createEVMClient, and set ui.headless to true:

import { createEVMClient, getInfuraRpcUrls } from '@metamask/connect-evm'

const evmClient = await createEVMClient({
dapp: {
name: 'My Dapp',
url: window.location.href,
},
api: {
supportedNetworks: {
...getInfuraRpcUrls({ infuraApiKey: '<YOUR_INFURA_API_KEY>' }),
},
},
ui: { headless: true },
})

2. Register a display_uri listener before connecting

The display_uri event fires on the EIP-1193 provider during the connecting phase with a one-time-use pairing URI. You must register the listener before calling connect, or you may miss the event:

const provider = evmClient.getProvider()

provider.on('display_uri', uri => {
showCustomQrModal(uri)
})

As an alternative to provider.on, you can pass a displayUri callback when initializing the client via eventHandlers (see the migration guide).

3. Connect and handle the result

Call connect to connect to MetaMask, and handle the result:

try {
await evmClient.connect({ chainIds: ['0x1'] })
hideCustomQrModal()
} catch (err) {
hideCustomQrModal()
if (err.code === 4001) {
// User rejected — show retry UI
} else {
console.error('Connection failed:', err)
}
}

Important considerations

URI is one-time-use

The pairing URI delivered by display_uri is a one-time-use token. Once used or expired, it cannot be reused. If the connection fails, call connect again to generate a fresh URI.

display_uri only fires during connecting

The event fires only while the client status is 'connecting'. After the connection resolves (success or error), display_uri stops firing.

Extension connections skip QR

When the MetaMask browser extension is installed and ui.preferExtension is true (the default), the SDK connects directly through the extension. No display_uri event fires because no QR code is needed.

To display the QR connection option even when the extension is available, set ui.preferExtension to false:

const evmClient = await createEVMClient({
dapp: { name: 'My Dapp', url: window.location.href },
api: {
supportedNetworks: {
...getInfuraRpcUrls({ infuraApiKey: '<YOUR_INFURA_API_KEY>' }),
},
},
ui: {
headless: true,
preferExtension: false,
},
})

Monitor connection status

The EVM client exposes a status property ('loaded' | 'pending' | 'connecting' | 'connected' | 'disconnected') you can read when updating UI.

Next steps