Save Gas and Get Typed Reverts: A Practical Guide to Solidity Custom Errors
Solidity custom errors save gas by replacing string reverts with typed, compact payloads. Learn how to declare, use, and verify them, plus the limits and common pitfalls in this practical guide.
04 Jun 2026, 00:12 UTC

Why Use Custom Errors?
When a function fails, the traditional approach is to revert("Error message"). That string is dynamically encoded into the transaction’s revert data, which costs gas. Solidity 0.8.4 introduced custom errors to replace string messages with a compact, typed payload. The result is a 4‑byte selector plus ABI‑encoded arguments, usually 30–50% cheaper than a string revert.
Declaring a Custom Error
Custom errors are declared with the error keyword, outside any function or constructor. They can carry typed parameters, just like function signatures.
pragma solidity ^0.8.6;
error InsufficientBalance(uint256 requested, uint256 available);
Here InsufficientBalance accepts two unsigned integers. The compiler generates a 4‑byte selector by hashing the error signature: InsufficientBalance(uint256,uint256).
Using the Error in Reverts
There are two common patterns:
- Direct revert:
revert InsufficientBalance(requested, available); - Require with error:
require(balance >= amount, InsufficientBalance(amount, balance));
Both produce the same revert payload. The error name is not stored on-chain; only its selector and encoded arguments are.
Practical Example
Below is a minimal ERC‑20‑like contract that uses a custom error for the transfer function.
pragma solidity ^0.8.6;
contract Token {
mapping(address => uint256) public balanceOf;
error InsufficientBalance(uint256 requested, uint256 available);
function transfer(address to, uint256 amount) external {
uint256 senderBalance = balanceOf[msg.sender];
if (senderBalance < amount) {
revert InsufficientBalance(amount, senderBalance);
}
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
}
}
Compile with solc 0.8.6 or a newer compiler. Deploy to a local Hardhat network, then call transfer with an amount larger than the sender’s balance to trigger the error.
Verifying the Revert Payload
- Send a transaction that fails (e.g.,
transfer(address(0xdead), 1000)when balance is 0). - Retrieve the transaction receipt via
ethers.getTransactionReceipt(txHash)(orweb3.eth.getTransactionReceipt). - Inspect the
statusfield: it should be0x0indicating failure. - Decode the
revertReason(thedatafield) usingethers.utils.defaultAbiCoder.decodewith the error’s ABI: - The decoded values should match the arguments passed to
revert.
const abi = [
"error InsufficientBalance(uint256 requested, uint256 available)"
];
const decoded = new ethers.utils.AbiCoder().decode(abi[0].inputs, receipt.revertReason);
console.log(decoded);
Gas Comparison
| Approach | Gas Used (approx.) |
|---|---|
| String revert ("Insufficient balance") | ~48,000 |
| Custom error (InsufficientBalance) | ~32,000 |
Actual numbers vary by network and optimizer settings, but the custom error consistently saves ~30–50% gas on revert paths.
Limitations and Common Pitfalls
- Compiler version: Custom errors require Solidity ≥ 0.8.4. Older compilers will reject the
errorkeyword. - Tooling support: Some older versions of Truffle, Hardhat, or ethers.js may not parse the ABI for custom errors, leading to decoding failures. Ensure your tooling is up‑to‑date.
- Bytecode bloat: Each distinct error adds a selector and type layout to the contract’s bytecode. Overusing custom errors in a single contract can increase deployment cost.
- ABI collisions: The 4‑byte selector is derived from the error signature. While collisions are astronomically unlikely, they are theoretically possible if two contracts share the same error name and parameter types.
- Interoperability: Contracts that need to interact with legacy code that expects string revert reasons may not handle custom errors gracefully.
- Obfuscation concerns: The selector is visible on-chain. If privacy of the error type is a concern, consider additional obfuscation or avoid exposing sensitive error names.
When to Use Custom Errors
Use them in production contracts where gas cost matters and the contract is compiled with Solidity ≥ 0.8.4. For prototypes, testing, or environments where tooling may not support custom errors, fallback to string reverts.
Conclusion
Custom errors give you typed, gas‑efficient revert reasons without sacrificing clarity. Declare them once, use them consistently, and verify the revert payload with your preferred library. Doing so yields cleaner bytecode, lower gas, and a better developer experience for both on‑chain and off‑chain tooling.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.