How to Use the MetaMask API: Connect Your dApp to Ethereum Wallets

Learn how to use the MetaMask API: detect window.ethereum, request accounts, sign EIP-712 data, switch chains, and use the MetaMask SDK in React and Node.

Ashley Innocent

Ashley Innocent

23 April 2026

How to Use the MetaMask API: Connect Your dApp to Ethereum Wallets

Apidog for Enterprise

On-Premises Deploy

SSO & RBAC

SOC 2 Compliant

Explore Apidog Enterprise

MetaMask is the default on-ramp to Ethereum for tens of millions of users, and if you run a dApp, the MetaMask API is the thing that sits between your UI and their signing keys. The “MetaMask API” is two things in a trench coat: the injected window.ethereum provider defined by EIP-1193, and the MetaMask SDK that extends that same surface to mobile apps, React Native, and Node.js backends. Learn the provider, and you have learned 80% of every wallet integration on the web.

This guide walks through detecting the provider, requesting accounts, reading the current chain, signing messages with personal_sign and EIP-712, sending transactions, adding or switching chains, and using the MetaMask SDK when you are outside a browser extension. You will also see where ethers.js v6 and viem fit as higher-level wrappers, and where Apidog plugs into the workflow when you want to test the underlying JSON-RPC calls without writing throwaway frontend code.

button

If you are building anything that touches wallets, bookmark this alongside our guide on the best crypto wallet API for a broader view of the provider landscape.

TL;DR

What is the MetaMask API?

The MetaMask API is the interface MetaMask exposes to web pages and apps for interacting with Ethereum and EVM-compatible chains. In a browser, the extension injects a provider object at window.ethereum, and that object follows the EIP-1193 standard. Any dApp that targets that standard works with MetaMask, Coinbase Wallet, Rabby, Frame, and dozens of others with zero code changes.

Outside the browser, the MetaMask SDK gives you the same provider shape in React Native, pure Node.js, desktop Electron apps, and even server-side scripts. The SDK handles the deep-linking and QR-code dance that lets a mobile MetaMask wallet sign transactions requested by a desktop or backend process. Under the hood it still speaks EIP-1193, so your app code barely changes.

MetaMask also ships Snaps, a plugin system that lets third parties extend the wallet with new chains, custom RPC methods, and account types. Snaps are out of scope here, but worth knowing if you want to support non-EVM chains or custom signing flows inside MetaMask itself.

Authentication and setup

There are no API keys for the provider itself. Authentication is the user approving each request in their wallet UI. You do need two things: a way to detect the provider, and a way to listen for changes.

Start with detection. The @metamask/detect-provider helper handles edge cases like multiple wallets being installed, but you can also check directly.

// Vanilla JS detection
import detectEthereumProvider from '@metamask/detect-provider';

const provider = await detectEthereumProvider({ mustBeMetaMask: true });

if (!provider) {
  alert('Please install MetaMask');
} else {
  console.log('MetaMask detected');
}

Once you have the provider, wire up event listeners before you request anything. Losing the accountsChanged event is the most common MetaMask integration bug.

window.ethereum.on('accountsChanged', (accounts) => {
  if (accounts.length === 0) {
    console.log('User disconnected');
  } else {
    console.log('Active account:', accounts[0]);
  }
});

window.ethereum.on('chainChanged', (chainId) => {
  // Best practice: reload the page on chain change
  window.location.reload();
});

For React apps, wagmi handles all of this for you and picks up MetaMask automatically via its injected connector.

Core endpoints

All provider calls go through window.ethereum.request({ method, params }). The method name is a JSON-RPC string, and params is an array or object depending on the method. Here are the calls that cover 95% of dApp integrations.

Request accounts and read chain

// Prompt the user to connect
const accounts = await window.ethereum.request({
  method: 'eth_requestAccounts',
});
const account = accounts[0];

// Read the current chain
const chainId = await window.ethereum.request({
  method: 'eth_chainId',
});

console.log(account, chainId); // '0x...' '0x1' (Ethereum mainnet)

The equivalent raw JSON-RPC call, which you can fire at any Ethereum node, looks like this with curl.

curl https://mainnet.infura.io/v3/YOUR_KEY \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'

For read-only calls you do not need MetaMask at all; node providers like Alchemy or Infura serve the same RPC. See our Alchemy API guide for the full picture on hosted Ethereum nodes.

Sign a simple message

personal_sign is the classic human-readable signature. It prefixes the message so users cannot be tricked into signing arbitrary transaction bytes.

const message = 'Sign in to Apidog at ' + new Date().toISOString();
const signature = await window.ethereum.request({
  method: 'personal_sign',
  params: [message, account],
});

Sign structured data with EIP-712

For anything complex, like a permit or a login challenge, use eth_signTypedData_v4. MetaMask renders the fields cleanly in the confirmation popup, which both protects users and builds trust.

const typedData = {
  domain: { name: 'Apidog Demo', version: '1', chainId: 1 },
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
    ],
    Login: [
      { name: 'wallet', type: 'address' },
      { name: 'nonce', type: 'uint256' },
    ],
  },
  primaryType: 'Login',
  message: { wallet: account, nonce: 42 },
};

const sig = await window.ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [account, JSON.stringify(typedData)],
});

Send a transaction

Transactions use eth_sendTransaction. MetaMask handles gas estimation and nonce management automatically.

const txHash = await window.ethereum.request({
  method: 'eth_sendTransaction',
  params: [{
    from: account,
    to: '0xRecipientAddressHere',
    value: '0x38d7ea4c68000', // 0.001 ETH in wei, hex
  }],
});

Switch or add a chain

EIP-3326 and EIP-3085 cover switching to a known chain and adding a new one.

// Switch to Polygon (chainId 137 = 0x89)
try {
  await window.ethereum.request({
    method: 'wallet_switchEthereumChain',
    params: [{ chainId: '0x89' }],
  });
} catch (err) {
  if (err.code === 4902) {
    // Chain not added yet
    await window.ethereum.request({
      method: 'wallet_addEthereumChain',
      params: [{
        chainId: '0x89',
        chainName: 'Polygon',
        rpcUrls: ['https://polygon-rpc.com'],
        nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 },
      }],
    });
  }
}

React with the MetaMask SDK

The SDK also works well in regular React when you want a single integration path that covers desktop extension, mobile deep-link, and in-app browser.

import { MetaMaskProvider, useSDK } from '@metamask/sdk-react';

function Connect() {
  const { sdk, connected, account } = useSDK();
  return (
    <button onClick={() => sdk?.connect()}>
      {connected ? account : 'Connect MetaMask'}
    </button>
  );
}

export default function App() {
  return (
    <MetaMaskProvider sdkOptions={{ dappMetadata: { name: 'My dApp' } }}>
      <Connect />
    </MetaMaskProvider>
  );
}

For production apps, wrap the raw provider in ethers.js v6 or viem. They give you typed contracts, ABI decoders, and better error messages while still talking to the same MetaMask API underneath. If you also need email or social login as a fallback, pair MetaMask with an embedded wallet via our Privy API guide.

Common errors and rate limits

MetaMask returns standard JSON-RPC error codes. The ones you will hit most often:

The provider itself has no rate limit, but the underlying RPC does. If you are proxying reads through Infura or Alchemy, you are bound by their tier. For fiat flows like ETH-to-USD conversion, most teams pair this integration with a fiat on-ramp and off-ramp API so users can top up without leaving the dApp.

MetaMask API pricing

The MetaMask extension and the SDK are free. There is no charge per connection, per signature, or per transaction. MetaMask earns revenue through swap fees when users trade inside the wallet and through the MetaMask Card, not from dApp developers.

Costs you will pay come from the RPC endpoint your dApp hits for reads. A free Alchemy or Infura tier handles most small apps; production dApps usually land between $49 and $299 per month for dedicated throughput.

Testing the MetaMask API with Apidog

Browser-based signing is hard to debug because the request flow spans an extension, a page, and sometimes a mobile deep-link. That is where Apidog earns its place in the wallet-dev toolkit. You can hit the raw JSON-RPC endpoint your dApp uses, confirm that eth_chainId and eth_getBalance return what you expect, and save the whole flow as a test suite.

Import the Ethereum JSON-RPC spec, set your node URL as an environment variable, and you have a reusable collection for every EVM chain. Apidog also mocks responses, so your frontend developers can build against a fake eth_sendTransaction while the smart contract is still in audit. For CI, you can run the same collection from the command line and fail the build if a response shape changes. If you have been fighting with Postman collections that never sync across the team, our guide on API testing without Postman in 2026 explains why Apidog handles multi-protocol dApp testing better.

Download Apidog to get started.

FAQ

Does the MetaMask API work on mobile?Yes. Use the MetaMask SDK, which deep-links to the mobile app for signing. The provider surface is identical to the browser extension, so your code stays the same. For a deeper comparison with other mobile wallet SDKs, see our best crypto wallet API roundup.

What is the difference between eth_sign, personal_sign, and eth_signTypedData_v4?eth_sign signs raw bytes and is dangerous; MetaMask warns users aggressively. personal_sign prefixes a human-readable message. eth_signTypedData_v4 signs structured EIP-712 data and shows fields cleanly in the MetaMask UI. Use the last two; avoid eth_sign.

Do I need a separate API key from MetaMask?No. The provider is free and keyless. You do need an RPC provider like Alchemy or Infura for reads outside the wallet, which have their own keys.

Can I use ethers.js or viem with MetaMask?Yes, both wrap the window.ethereum provider. Ethers v6 exposes BrowserProvider(window.ethereum), and viem has createWalletClient({ transport: custom(window.ethereum) }). Most production dApps use one of the two.

What happens if a user has multiple wallets installed?MetaMask implements EIP-6963, so dApps can detect all installed wallets instead of fighting over window.ethereum. Wagmi and RainbowKit handle this automatically.

Is MetaMask Snaps ready for production?Yes, Snaps went generally available in 2024. Most production use is for non-EVM chain support, custom transaction insights, and hardware wallet integrations.

Explore more

How to Validate Your API Against Its Spec Without Dredd

How to Validate Your API Against Its Spec Without Dredd

Dredd checks your running API against its spec, but needs a hooks file and a loose spec. Here is an alternative that keeps the spec and tests in one npm CLI.

15 June 2026

How to Install the Apidog CLI With an AI Coding Agent

How to Install the Apidog CLI With an AI Coding Agent

Let your AI coding agent install the Apidog CLI for you. Exact prompts for Claude Code, Cursor, and Copilot, the commands they run, and how to verify each step.

15 June 2026

How to Run Automated API Tests in Azure Pipelines (Step-by-Step)

How to Run Automated API Tests in Azure Pipelines (Step-by-Step)

Run automated API tests in Azure Pipelines step by step: design scenarios in Apidog, trigger them with the Apidog CLI, and fail the build on regressions.

15 June 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs