|
| 1 | +--- |
| 2 | +title: 'Use Headless Mode - MetaMask Connect EVM' |
| 3 | +sidebar_label: Use headless mode |
| 4 | +description: Render a custom QR code or connection UI by using headless mode with MetaMask Connect EVM. |
| 5 | +keywords: |
| 6 | + [headless, QR code, custom UI, display_uri, evm, MetaMask, Connect, connection, modal, deeplink] |
| 7 | +--- |
| 8 | + |
| 9 | +# Use headless mode |
| 10 | + |
| 11 | +By default, MetaMask Connect renders its own QR code modal when connecting to MetaMask Mobile |
| 12 | +via MetaMask Wallet Protocol (MWP). |
| 13 | +Headless mode suppresses this built-in modal so you can render your own connection UI. |
| 14 | + |
| 15 | +Use headless mode when you want to: |
| 16 | + |
| 17 | +- Display a custom-styled QR code that matches your dapp's design. |
| 18 | +- Show the connection URI in a different format (for example, a deeplink button instead of a QR code). |
| 19 | +- Integrate the connection flow into an existing modal or onboarding wizard. |
| 20 | + |
| 21 | +## Prerequisites |
| 22 | + |
| 23 | +Follow Step 1 of the [quickstart](../quickstart/javascript.md) to install the EVM client. |
| 24 | + |
| 25 | +## Steps |
| 26 | + |
| 27 | +### 1. Initialize the client with headless mode |
| 28 | + |
| 29 | +Initialize an EVM client using [`createEVMClient`](../reference/methods.md#createevmclient), and set `ui.headless` to `true`: |
| 30 | + |
| 31 | +```javascript |
| 32 | +import { createEVMClient, getInfuraRpcUrls } from '@metamask/connect-evm' |
| 33 | + |
| 34 | +const evmClient = await createEVMClient({ |
| 35 | + dapp: { |
| 36 | + name: 'My Dapp', |
| 37 | + url: window.location.href, |
| 38 | + }, |
| 39 | + api: { |
| 40 | + supportedNetworks: { |
| 41 | + ...getInfuraRpcUrls({ infuraApiKey: '<YOUR_INFURA_API_KEY>' }), |
| 42 | + }, |
| 43 | + }, |
| 44 | + ui: { headless: true }, |
| 45 | +}) |
| 46 | +``` |
| 47 | + |
| 48 | +### 2. Register a `display_uri` listener before connecting |
| 49 | + |
| 50 | +The `display_uri` event fires on the EIP-1193 [provider](../reference/provider-api.md) during the connecting phase with a one-time-use pairing URI. |
| 51 | +You **must** register the listener before calling `connect`, or you may miss the event: |
| 52 | + |
| 53 | +```javascript |
| 54 | +const provider = evmClient.getProvider() |
| 55 | + |
| 56 | +provider.on('display_uri', uri => { |
| 57 | + showCustomQrModal(uri) |
| 58 | +}) |
| 59 | +``` |
| 60 | + |
| 61 | +As an alternative to `provider.on`, you can pass a `displayUri` callback when initializing the client via [`eventHandlers`](../reference/methods.md#createevmclient) (see the [migration guide](migrate-from-sdk.md#6-update-event-handling)). |
| 62 | + |
| 63 | +### 3. Connect and handle the result |
| 64 | + |
| 65 | +Call [`connect`](../reference/methods.md#connect) to connect to MetaMask, and handle the result: |
| 66 | + |
| 67 | +```javascript |
| 68 | +try { |
| 69 | + await evmClient.connect({ chainIds: ['0x1'] }) |
| 70 | + hideCustomQrModal() |
| 71 | +} catch (err) { |
| 72 | + hideCustomQrModal() |
| 73 | + if (err.code === 4001) { |
| 74 | + // User rejected — show retry UI |
| 75 | + } else { |
| 76 | + console.error('Connection failed:', err) |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +## Important considerations |
| 82 | + |
| 83 | +### URI is one-time-use |
| 84 | + |
| 85 | +The pairing URI delivered by `display_uri` is a one-time-use token. |
| 86 | +Once used or expired, it cannot be reused. |
| 87 | +If the connection fails, call `connect` again to generate a fresh URI. |
| 88 | + |
| 89 | +### `display_uri` only fires during connecting |
| 90 | + |
| 91 | +The event fires only while the client status is `'connecting'`. |
| 92 | +After the connection resolves (success or error), `display_uri` stops firing. |
| 93 | + |
| 94 | +### Extension connections skip QR |
| 95 | + |
| 96 | +When the MetaMask browser extension is installed and `ui.preferExtension` is `true` (the default), |
| 97 | +the SDK connects directly through the extension. |
| 98 | +No `display_uri` event fires because no QR code is needed. |
| 99 | + |
| 100 | +To display the QR connection option even when the extension is available, set `ui.preferExtension` to `false`: |
| 101 | + |
| 102 | +```javascript |
| 103 | +const evmClient = await createEVMClient({ |
| 104 | + dapp: { name: 'My Dapp', url: window.location.href }, |
| 105 | + api: { |
| 106 | + supportedNetworks: { |
| 107 | + ...getInfuraRpcUrls({ infuraApiKey: '<YOUR_INFURA_API_KEY>' }), |
| 108 | + }, |
| 109 | + }, |
| 110 | + ui: { |
| 111 | + headless: true, |
| 112 | + preferExtension: false, |
| 113 | + }, |
| 114 | +}) |
| 115 | +``` |
| 116 | + |
| 117 | +### Monitor connection status |
| 118 | + |
| 119 | +The EVM client exposes a [`status`](../reference/methods.md#properties) property |
| 120 | +(`'loaded' | 'pending' | 'connecting' | 'connected' | 'disconnected'`) you can read when updating UI. |
| 121 | + |
| 122 | +## Next steps |
| 123 | + |
| 124 | +- [Send transactions](send-transactions/index.md) in your JavaScript or Wagmi dapp. |
| 125 | +- [Sign data](sign-data/index.md) with the connected session. |
| 126 | +- See the [EVM methods reference](../reference/methods.md) for the full API. |
0 commit comments