Independent review. This site is not the official website and is not affiliated with, endorsed by, or operated by the wallet vendor reviewed here. Never enter your seed phrase or private keys on any third-party site.

Connect MetaMask in web3.js and React Apps

Try Tangem secure wallet →

Overview

This guide shows practical, code-first ways to connect MetaMask to web3.js and React applications. I write from hands-on use: I’ve been connecting dApps to MetaMask daily for months while building small tools and testing on local chains. Expect clear examples, common gotchas, and links to deeper setup pages (like installing the extension or adding custom networks).

And yes — you can handle account and chain changes gracefully. It takes a bit of event wiring, but once set up it feels seamless.

If you want quick setup instructions for end users, see install-metamask-extension and install-metamask-mobile-app.

Prerequisites

  • A user with MetaMask installed (extension or mobile). See install-metamask-extension.
  • web3.js in your project (this guide uses web3 1.x). Install via npm: npm install web3.
  • Optional: a local test node (Ganache). See connect-ganache-local.

But what if a user only has a mobile wallet? Use WalletConnect as an alternative (see connect-walletconnect).

Try Tangem secure wallet →

How MetaMask exposes a provider (EIP-1193)

MetaMask injects a provider on window.ethereum in modern browsers. Older sites sometimes look for window.web3; prefer the new API.

Key methods you'll use:

  • provider.request({ method: 'eth_requestAccounts' }) — ask the user to connect accounts.
  • provider.request({ method: 'eth_chainId' }) — returns chain ID as a hex string.
  • provider.on('accountsChanged', handler) — event when user switches accounts.
  • provider.on('chainChanged', handler) — event when network changes (reload often recommended).

(Yes, chain IDs are hex — remember to convert when matching.)

web3.js: connect MetaMask (step by step)

Below is a minimal example showing javascript connect MetaMask and set up web3.js.

import Web3 from 'web3';

async function connectMetaMask() {
  if (typeof window.ethereum === 'undefined') {
    throw new Error('MetaMask not installed');
  }

  const provider = window.ethereum;

  try {
    // Request account access
    const accounts = await provider.request({ method: 'eth_requestAccounts' });
    const web3 = new Web3(provider);

    // Use web3 as usual
    const balance = await web3.eth.getBalance(accounts[0]);
    console.log('Connected account:', accounts[0]);
    console.log('Balance (wei):', balance);

    return { web3, account: accounts[0] };
  } catch (err) {
    // User rejected or other error
    console.error('Connection error', err);
    throw err;
  }
}

Error code 4001 means the user rejected the request. Handle it politely in the UI.

React: connect MetaMask (hook pattern)

In React, a simple hook keeps state tidy and subscribes to provider events. What I've found: handling account and chain changes early prevents weird UX where the dApp is still tied to an old account.

import { useState, useEffect, useCallback } from 'react';
import Web3 from 'web3';

export function useMetaMask() {
  const [web3, setWeb3] = useState(null);
  const [account, setAccount] = useState(null);
  const [chainId, setChainId] = useState(null);

  useEffect(() => {
    const provider = window.ethereum;
    if (!provider) return;

    const web3Instance = new Web3(provider);
    setWeb3(web3Instance);

    provider.request({ method: 'eth_accounts' }).then(accounts => {
      if (accounts.length) setAccount(accounts[0]);
    });

    provider.request({ method: 'eth_chainId' }).then(id => setChainId(id));

    const handleAccounts = (accounts) => setAccount(accounts[0] || null);
    const handleChain = (id) => {
      setChainId(id);
      // Some apps reload on chain change to reset state
      // window.location.reload();
    };

    provider.on('accountsChanged', handleAccounts);
    provider.on('chainChanged', handleChain);

    return () => {
      provider.removeListener('accountsChanged', handleAccounts);
      provider.removeListener('chainChanged', handleChain);
    };
  }, []);

  const connect = useCallback(async () => {
    if (!window.ethereum) throw new Error('No provider');
    const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
    setAccount(accounts[0]);
  }, []);

  return { web3, account, chainId, connect };
}

Use this hook in a component to show a connect button and the current account.

Quick API reference (cheat sheet)

Method / Event Purpose Example / Note
eth_requestAccounts Ask user to connect accounts provider.request({ method: 'eth_requestAccounts' })
eth_accounts Get already-connected accounts provider.request({ method: 'eth_accounts' })
eth_chainId Read chain ID (hex) provider.request({ method: 'eth_chainId' })
wallet_switchEthereumChain Ask wallet to switch network params: [{ chainId: '0x1' }]
wallet_addEthereumChain Add custom network to wallet full chain params required
accountsChanged (event) Fired when user switches accounts provider.on('accountsChanged', fn)
chainChanged (event) Fired when network changes provider.on('chainChanged', fn)

Screenshot placeholder: connect prompt

Common pitfalls & troubleshooting

  • provider is undefined: MetaMask not installed or blocked by an extension. Link users to install-metamask-extension.
  • App still shows old account after switch: subscribe to accountsChanged. Short sessions solve state drift.
  • Wrong network: the provider returns a chainId you didn't expect. Offer a UI to request a chain switch (see below) and link to unsupported-networks.
  • Local test nodes: if you use Ganache, set RPC and chainId correctly and see connect-ganache-local.

For common connect button issues see connect-button-troubleshoot and troubleshooting-dapp-connections.

Security notes for developers and users

MetaMask is a software wallet (hot wallet). That means convenience and risk coexist. I once approved an unlimited token allowance by accident; I had to revoke it through an external tool. Learn from my mistake: always let users inspect approvals before signing.

Developer checklist:

  • Never request signatures for actions that users can't verify. Ask for minimal scopes.
  • Display readable summaries of transactions (amounts, recipient, token contract address).
  • Use third-party transaction simulation services if you want to show estimated outcomes (for example, before sending a complex DeFi call).

User checklist:

Advanced: switch/add networks, EIP-1559, L2s

Programmatic chain switching is supported with wallet_switchEthereumChain. Example:

await window.ethereum.request({
  method: 'wallet_switchEthereumChain',
  params: [{ chainId: '0x89' }], // Polygon
});

If the chain is unknown to the wallet you may receive error 4902. Then call wallet_addEthereumChain with the chain parameters.

EIP-1559 is the gas model you’ll encounter on many EVM-compatible networks. When you build transactions you can set maxFeePerGas and maxPriorityFeePerGas instead of legacy gasPrice. For Layer 2s (L2) gas behavior and savings, see layer2-networks and gas-fees-eip1559.

Account abstraction and smart contract wallets can interact with MetaMask, but some flows require custom signing and relayers. Explore smart-contract-wallets-aa if you plan to support gasless experiences.

FAQs

Q: Is it safe to keep crypto in a hot wallet?

A: Hot wallets trade some security for convenience. They’re fine for daily use and DeFi interactions. For large holdings, consider cold storage or a hardware wallet (see connect-ledger). What I've found: split funds — daily spending in a hot wallet, longer-term holdings in cold storage.

Q: How do I revoke token approvals?

A: MetaMask doesn't always expose a one-click revocation UI. Use a trusted approval manager or review contract allowances on-chain. See our guide token-approvals-revoke for safe steps.

Q: What happens if I lose my phone?

A: If you used a seed phrase to create the wallet, restore on another device with the seed phrase. If you relied on cloud or social recovery, follow those recovery guides. Read backup-recovery-seed and backup-cloud-vs-paper for options.

Conclusion and next steps

Connecting MetaMask with web3.js and React requires a few explicit steps: detect provider, request accounts, instantiate web3, and subscribe to provider events. In my experience, the small upfront work saves bugs later. Want to keep building? Start a local node (connect-ganache-local), or explore programmatic network switching (add-custom-network) and developer APIs (metamask-api-connect, developers-connect).

But don't rush key management. And test all flows on testnets first.

Happy building — and if you hit a specific error, check troubleshooting-dapp-connections or ask here and I’ll try to help.

Try Tangem secure wallet →