Skip to content

TypeScript

Requirements

Wagmi is designed to be as type-safe as possible! Things to keep in mind:

  • Types currently require using TypeScript >=5.0.4.
  • TypeScript doesn't follow semver and often introduces breaking changes in minor releases.
  • Changes to types in this repository are considered non-breaking and are usually released as patch changes (otherwise every type enhancement would be a major version!).
  • It is highly recommended that you lock your wagmi and typescript versions to specific patch releases and upgrade with the expectation that types may be fixed or upgraded between any release.
  • The non-type-related public API of Wagmi still follows semver very strictly.

To ensure everything works correctly, make sure your tsconfig.json has strict mode set to true.

json
{
  "compilerOptions": {
    "strict": true
  }
}
{
  "compilerOptions": {
    "strict": true
  }
}

Config Types

By default React Context does not work well with type inference. To support strong type-safety across the React Context boundary, there are two options available:

  • Declaration merging to "register" your config globally with TypeScript.
  • config property to pass your config directly to hooks.

Declaration Merging

Declaration merging allows you to "register" your config globally with TypeScript. The Register type enables Wagmi to infer types in places that wouldn't normally have access to type info via React Context alone.

To set this up, add the following declaration to your project. Below, we co-locate the declaration merging and the config set up.

ts
import { createConfig, http } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'

declare module 'wagmi' { 
  interface Register { 
    config: typeof config 
  } 
} 

export const config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
    [mainnet.id]: http(),
    [sepolia.id]: http(),
  },
})
import { createConfig, http } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'

declare module 'wagmi' { 
  interface Register { 
    config: typeof config 
  } 
} 

export const config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
    [mainnet.id]: http(),
    [sepolia.id]: http(),
  },
})

Since the Register type is global, you only need to add it once in your project. Once set up, you will get strong type-safety across your entire project. For example, query hooks will type chainId based on your config's chains.

ts
ts
import { useBlockNumber } from 'wagmi'
 
useBlockNumber({ chainId: 123 })
Type '123' is not assignable to type '1 | 11155111 | undefined'.2322Type '123' is not assignable to type '1 | 11155111 | undefined'.
ts
import { useBlockNumber } from 'wagmi'
 
useBlockNumber({ chainId: 123 })
Type '123' is not assignable to type '1 | 11155111 | undefined'.2322Type '123' is not assignable to type '1 | 11155111 | undefined'.

You just saved yourself a runtime error and you didn't even need to pass your config. 🎉

Hook config Property

For cases where you have more than one Wagmi config or don't want to use the declaration merging approach, you can pass a specific config directly to hooks via the config property.

ts
import { createConfig, http } from 'wagmi'
import { mainnet, optimism } from 'wagmi/chains'

export const configA = createConfig({ 
  chains: [mainnet], 
  transports: { 
    [mainnet.id]: http(), 
  }, 
}) 

export const configB = createConfig({ 
  chains: [optimism], 
  transports: { 
    [optimism.id]: http(), 
  }, 
}) 
import { createConfig, http } from 'wagmi'
import { mainnet, optimism } from 'wagmi/chains'

export const configA = createConfig({ 
  chains: [mainnet], 
  transports: { 
    [mainnet.id]: http(), 
  }, 
}) 

export const configB = createConfig({ 
  chains: [optimism], 
  transports: { 
    [optimism.id]: http(), 
  }, 
}) 

As you expect, chainId is inferred correctly for each config.

ts
ts
import { useBlockNumber } from 'wagmi'
 
useBlockNumber({ chainId: 123, config: configA })
Type '123' is not assignable to type '1'.2322Type '123' is not assignable to type '1'.
useBlockNumber({ chainId: 123, config: configB })
Type '123' is not assignable to type '10'.2322Type '123' is not assignable to type '10'.
ts
import { useBlockNumber } from 'wagmi'
 
useBlockNumber({ chainId: 123, config: configA })
Type '123' is not assignable to type '1'.2322Type '123' is not assignable to type '1'.
useBlockNumber({ chainId: 123, config: configB })
Type '123' is not assignable to type '10'.2322Type '123' is not assignable to type '10'.

This approach is more explicit, but works well for advanced use-cases, if you don't want to use React Context or declaration merging, etc.

Const-Assert ABIs & Typed Data

Wagmi can infer types based on ABIs and EIP-712 Typed Data definitions, powered by Viem and ABIType. This achieves full end-to-end type-safety from your contracts to your frontend and enlightened developer experience by autocompleting ABI item names, catching misspellings, inferring argument and return types (including overloads), and more.

For this to work, you must either const-assert ABIs and Typed Data (more info below) or define them inline. For example, useReadContract's abi configuration parameter:

ts
const { data } = useReadContract({
  abi: […], // <--- defined inline 
})
const { data } = useReadContract({
  abi: […], // <--- defined inline 
})
ts
const abi = [] as const // <--- const assertion 
const { data } = useReadContract({ abi })
const abi = [] as const // <--- const assertion 
const { data } = useReadContract({ abi })

If type inference isn't working, it's likely you forgot to add a const assertion or define the configuration parameter inline. Also, make sure your ABIs, Typed Data definitions, and TypeScript configuration are valid and set up correctly.

TIP

Unfortunately TypeScript doesn't support importing JSON as const yet. Check out the Wagmi CLI to help with this! It can automatically fetch ABIs from Etherscan and other block explorers, resolve ABIs from your Foundry/Hardhat projects, generate React Hooks, and more.

Anywhere you see the abi or types configuration property, you can likely use const-asserted or inline ABIs and Typed Data to get type-safety and inference. These properties are also called out in the docs.

Here's what useReadContract looks like with and without a const-asserted abi property.

ts
ts
import { useReadContract } from 'wagmi'
 
const { data } = useReadContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: erc721Abi,
functionName: 'balanceOf',
(property) functionName?: "balanceOf" | "isApprovedForAll" | "getApproved" | "ownerOf" | "tokenURI" | undefined
args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
(property) args?: ((readonly [`0x${string}`] | Widen<readonly [`0x${string}`]> | undefined) & Widen<readonly [`0x${string}`]>) | undefined
})
data
const data: bigint | undefined
ts
import { useReadContract } from 'wagmi'
 
const { data } = useReadContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: erc721Abi,
functionName: 'balanceOf',
(property) functionName?: "balanceOf" | "isApprovedForAll" | "getApproved" | "ownerOf" | "tokenURI" | undefined
args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
(property) args?: ((readonly [`0x${string}`] | Widen<readonly [`0x${string}`]> | undefined) & Widen<readonly [`0x${string}`]>) | undefined
})
data
const data: bigint | undefined
ts
ts
import { useReadContract } from 'wagmi'
 
const { data } = useReadContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: erc721Abi,
functionName: 'balanceOf',
(property) functionName?: string | undefined
args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
(property) args?: readonly unknown[] | undefined
})
data
const data: unknown
ts
import { useReadContract } from 'wagmi'
 
const { data } = useReadContract({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: erc721Abi,
functionName: 'balanceOf',
(property) functionName?: string | undefined
args: ['0xA0Cf798816D4b9b9866b5330EEa46a18382f251e'],
(property) args?: readonly unknown[] | undefined
})
data
const data: unknown

You can prevent runtime errors and be more productive by making sure your ABIs and Typed Data definitions are set up appropriately. 🎉

ts
ts
import { useReadContract } from 'wagmi'
 
useReadContract({
abi: erc721Abi,
functionName: 'balanecOf',
Type '"balanecOf"' is not assignable to type '"balanceOf" | "isApprovedForAll" | "getApproved" | "ownerOf" | "tokenURI" | undefined'. Did you mean '"balanceOf"'?2820Type '"balanecOf"' is not assignable to type '"balanceOf" | "isApprovedForAll" | "getApproved" | "ownerOf" | "tokenURI" | undefined'. Did you mean '"balanceOf"'?
})
ts
import { useReadContract } from 'wagmi'
 
useReadContract({
abi: erc721Abi,
functionName: 'balanecOf',
Type '"balanecOf"' is not assignable to type '"balanceOf" | "isApprovedForAll" | "getApproved" | "ownerOf" | "tokenURI" | undefined'. Did you mean '"balanceOf"'?2820Type '"balanecOf"' is not assignable to type '"balanceOf" | "isApprovedForAll" | "getApproved" | "ownerOf" | "tokenURI" | undefined'. Did you mean '"balanceOf"'?
})

Configure Internal Types

For advanced use-cases, you may want to configure Wagmi's internal types. Most of Wagmi's types relating to ABIs and EIP-712 Typed Data are powered by ABIType. See the ABIType docs for more info on how to configure types.

Released under the MIT License.