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.
Set up headless mode
1. Configure the client with headless mode
Initialize a MetaMask Connect Multichain client, and set ui.headless to true in the configuration options:
import { createMultichainClient, getInfuraRpcUrls } from '@metamask/connect-multichain'
const client = await createMultichainClient({
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 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:
client.on('display_uri', uri => {
showCustomQrModal(uri)
})
3. Connect and handle the result
try {
await client.connect(['eip155:1', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], [])
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 client = await createMultichainClient({
dapp: { name: 'My Dapp', url: window.location.href },
ui: {
headless: true,
preferExtension: false,
},
})
Monitor connection status
Use the stateChanged event to track the connection lifecycle and update your UI accordingly:
client.on('stateChanged', status => {
// status: 'loaded' | 'pending' | 'connecting' | 'connected' | 'disconnected'
switch (status) {
case 'connecting':
showLoadingIndicator()
break
case 'connected':
hideCustomQrModal()
showConnectedUI()
break
case 'disconnected':
showDisconnectedUI()
break
}
})
Next steps
- Send transactions on EVM and Solana using
invokeMethod. - Sign messages on EVM and Solana using
invokeMethod. - See the Multichain methods reference for the full API.