This page focuses on developer integration patterns for MetaMask: how browser-based dApps ask a software wallet for accounts, signatures, and transactions. I use MetaMask daily for testing and small trades (so I speak from hands-on experience). What I've found is that most problems come from lifecycle events — accounts or chain changes — and from assuming the wallet behaves like a backend RPC node.
Who this guide is for
Who should look elsewhere
If you haven't installed the extension or mobile app yet, see install-metamask-extension and install-metamask-mobile-app.
MetaMask injects an EIP-1193-compatible provider into web pages as window.ethereum. You request access with a method call (eg. eth_requestAccounts) and then subscribe to provider events (accountsChanged, chainChanged). Simple. But there are details: the provider is a bridge to the user's private keys in the hot wallet. Requests must be user-initiated for UX and security reasons.
Events you must handle: accountsChanged, chainChanged, and disconnect. Do it on a testnet first.
If you want web3 js connect MetaMask, here's a minimal flow with web3.js v1.x.
import Web3 from 'web3';
async function connectWithWeb3() {
if (!window.ethereum) throw new Error('No injected provider');
const web3 = new Web3(window.ethereum);
try {
// ask user to connect
await window.ethereum.request({ method: 'eth_requestAccounts' });
const accounts = await web3.eth.getAccounts();
return { web3, account: accounts[0] };
} catch (err) {
throw err; // user rejected or other error
}
}
Listen for changes:
window.ethereum.on('accountsChanged', (accounts) => { /* update UI */ });
window.ethereum.on('chainChanged', (chainId) => { window.location.reload(); });
Many prefer ethers for its smaller API surface and utility helpers. For react connect MetaMask or plain JS this pattern is common.
import { ethers } from 'ethers';
async function connectWithEthers() {
if (!window.ethereum) throw new Error('No provider');
const provider = new ethers.providers.Web3Provider(window.ethereum, 'any');
await provider.send('eth_requestAccounts', []);
const signer = provider.getSigner();
const address = await signer.getAddress();
return { provider, signer, address };
}
Signing a message (useful for auth):
const signature = await signer.signMessage('Login request: 167244');
Server-side verification is a standard pattern (see below).
A simple React button that asks MetaMask to connect is the most common UX. Below is the simplest pattern (no external hooks):
import React, { useState } from 'react';
import { ethers } from 'ethers';
export default function ConnectButton() {
const [addr, setAddr] = useState(null);
async function connect() {
try {
const { signer } = await connectWithEthers();
setAddr(await signer.getAddress());
} catch (e) {
console.error(e);
}
}
return <button onClick={connect}>{addr || 'Connect MetaMask'}</button>;
}
If you prefer structured tooling, compare connect-web3-react, web3modal connect MetaMask, or wagmi connect MetaMask for pre-built hooks and modal flows. React Native cannot directly access the browser extension — use WalletConnect or deep linking to the mobile app instead (see walletconnect-guide).
Node.js cannot directly talk to a browser extension. So how do servers "connect to MetaMask"? The common pattern is:
Node verification (ethers):
const { ethers } = require('ethers');
function verify(message, signature) {
return ethers.utils.verifyMessage(message, signature); // returns address
}
Python verification (web3.py / eth-account):
from eth_account.messages import encode_defunct
from eth_account import Account
def verify(message, signature):
msg = encode_defunct(text=message)
return Account.recover_message(msg, signature=signature)
For on-chain reads/writes from a server you use an RPC node (Infura, Alchemy, or self-hosted). But that is separate from MetaMask — which remains a client signer.
Tooling can simplify UX. web3modal connect MetaMask provides a modal picker for many wallets. WalletConnect links mobile wallets (useful for react native connect MetaMask flows). wagmi exposes React hooks for providers and signers.
Table: quick comparison
| Integration | Best for | Notes |
|---|---|---|
| web3.js | Classic dApps | Familiar API, larger bundle |
| ethers.js | Modern JS apps | Utilities for signatures and ABI parsing |
| web3modal / WalletConnect | Multi-wallet UX | Adds mobile support (deep links) |
| wagmi / web3-react | React apps | Hook-based state management |
For local development see connect-ganache-local and connect-remix.
For UI actions around approvals, link to token-approvals-revoke and security-best-practices.
For common UI problems see connect-button-troubleshoot and troubleshooting-dapp-connections.
Connecting to MetaMask is straightforward once you accept two realities: the provider lives in the browser, and signing belongs to the user. Which library you choose (web3.js, ethers, or a React toolkit) depends on your stack and team preferences.
Try these steps locally: wire up a simple connect button, request a signature, and verify it on a small Node or Python endpoint. And if you want to expand later, check the guides for connecting to networks (connect-to-networks, add-custom-network) and mobile flows (connect-walletconnect).
If you'd like, follow the hands-on examples here and then test against a local chain — it's the safest way to learn. Happy building.