OpenZeppelin Documentation
OpenZeppelin Contracts is the standard library for secure smart contracts. It provides audited implementations of ERC standards, access control modules, upgradeable patterns, and security primitives you can reuse instead of reinventing.
What is OpenZeppelin
OpenZeppelin Contracts is a library of reusable, audited smart contracts for Ethereum and EVM-compatible chains. Instead of writing low-level token, access control, or upgrade logic yourself, you inherit and configure the provided contracts.
- Standard implementations of ERC20, ERC721, ERC1155 and more.
- Access control patterns: Ownable, AccessControl, role-based permissions.
- Security primitives: ReentrancyGuard, Pausable, SafeERC20.
- Upgradeable variants compatible with proxy patterns.
Writing your own ERC20 or ERC721 implementation from scratch is rarely a good idea. Using OpenZeppelin reduces attack surface and makes your contracts more familiar to other developers.
Installation
The contracts are distributed as an npm package. You install it once and import what you need in Solidity.
Hardhat / Node projects
npm install @openzeppelin/contracts
# or
pnpm add @openzeppelin/contracts
Foundry projects
forge install OpenZeppelin/openzeppelin-contracts
For upgradeable contracts, use the upgradeable package:
npm install @openzeppelin/contracts-upgradeable
# Foundry:
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
Contracts Wizard
The OpenZeppelin Contracts Wizard is an interactive web tool that lets you configure a token or access control setup and generates Solidity code using the official library.
- Choose ERC20 / ERC721 / ERC1155.
- Select features: mintable, burnable, snapshots, permit, votes, etc.
- Choose between regular or upgradeable variants.
- Copy the generated contract into your project and adjust as needed.
A common workflow is: prototype with the Wizard, paste the contract into your repo, rename it and gradually refactor to fit your protocol while keeping the inherited base classes.
Project setup
For a typical ERC20 or ERC721 project with Hardhat or Foundry, you:
- Install @openzeppelin/contracts (and optionally -upgradeable).
- Pick a base contract (e.g. ERC20, ERC721).
- Inherit from it and implement constructor / initializer with your parameters.
- Add access control (owner, roles, or both).
- Write tests around minting, burning, role restrictions, and edge cases.
ERC20 Tokens
OpenZeppelin’s ERC20 contract implements the ERC20 standard with hooks and extensions. You almost never implement ERC20 directly yourself; you inherit ERC20 and configure it.
Basic ERC20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
_mint(msg.sender, 1_000_000 ether);
}
}
Common extensions:
- ERC20Burnable — token holders can burn their own tokens.
- ERC20Pausable — pausable transfers via Pausable.
- ERC20Permit — EIP-2612 approvals via signatures.
- ERC20Votes — voting power for governance.
ERC20 with permit + burnable
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GovernanceToken is ERC20Burnable, ERC20Permit, Ownable {
constructor()
ERC20("GovernanceToken", "GOV")
ERC20Permit("GovernanceToken")
Ownable(msg.sender)
{
_mint(msg.sender, 10_000_000 ether);
}
}
When your contracts interact with arbitrary ERC20 tokens, use SafeERC20 to handle non-standard return values and avoid stuck tokens.
ERC721 NFTs
OpenZeppelin provides a modular ERC721 implementation with enumerable, URI storage, and safe minting.
Basic ERC721
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, Ownable {
uint256 public nextId;
constructor() ERC721("MyNFT", "MNFT") Ownable(msg.sender) {}
function mint(address to) external onlyOwner {
_safeMint(to, nextId);
nextId++;
}
}
Common extensions:
- ERC721URIStorage — per-token metadata URI storage.
- ERC721Enumerable — on-chain enumeration (holders and token list).
- ERC721Burnable — burnable NFTs.
- ERC721Votes — governance voting power by NFT ownership.
ERC721Enumerable keeps extra on-chain state for enumeration. It increases gas costs of mint / transfer. For large collections, consider off-chain indexing instead.
ERC1155 Multi-tokens
ERC1155 is a multi-token standard that can represent fungible and non-fungible tokens within one contract.
Basic ERC1155
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GameItems is ERC1155, Ownable {
uint256 public constant GOLD = 0;
uint256 public constant SWORD = 1;
constructor() ERC1155("https://example.com/metadata/{id}.json") Ownable(msg.sender) {
_mint(msg.sender, GOLD, 1000, "");
_mint(msg.sender, SWORD, 10, "");
}
}
ERC1155 is useful for gaming items, semi-fungible assets, and collections where you need batches and multiple IDs.
Ownable & roles
The simplest access control pattern is Ownable. It defines a single owner address with a modifier onlyOwner.
Basic Ownable usage
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
contract Example is Ownable {
constructor() Ownable(msg.sender) {}
function sensitiveAction() external onlyOwner {
// restricted logic
}
}
For many simple tokens or apps with a single admin, Ownable is enough. For more complex permissions, use AccessControl.
AccessControl
AccessControl provides role-based permissions. Roles are identified by bytes32 identifiers and can be granted and revoked.
Basic AccessControl setup
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract RoleBasedExample is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
// mint logic
}
}
Best practices:
- Use DEFAULT_ADMIN_ROLE sparingly. It can grant and revoke other roles.
- Create specific roles for each action (MINTER_ROLE, PAUSER_ROLE, etc.).
- Consider using multisig or timelock as the admin for critical roles.
Pausable, ReentrancyGuard & security modules
OpenZeppelin provides a set of modules that help protect common patterns.
Pausable
Pausable lets you add an emergency stop to functions. Typically used on transfer, mint, or critical operations.
import "@openzeppelin/contracts/security/Pausable.sol";
contract MyContract is Pausable {
function pause() external /* onlyRole(PAUSER_ROLE) */ {
_pause();
}
function unpause() external /* onlyRole(PAUSER_ROLE) */ {
_unpause();
}
function doSomething() external whenNotPaused {
// ...
}
}
ReentrancyGuard
Protects functions from reentrancy attacks by using a simple state lock.
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Vault is ReentrancyGuard {
function withdraw(uint256 amount) external nonReentrant {
// checks
// effects
// interactions
}
}
SafeERC20
Use SafeERC20 wrapper to safely call ERC20 tokens with inconsistent return values.
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract Payment {
using SafeERC20 for IERC20;
function pay(IERC20 token, address to, uint256 amount) external {
token.safeTransfer(to, amount);
}
}
Upgrade patterns
OpenZeppelin supports upgradeable contracts via proxy patterns. The two main approaches are:
- Transparent proxies — admin and user separation, classic pattern.
- UUPS proxies — upgrade logic in the implementation contract itself.
You cannot use regular constructors and must follow strict rules around storage layout. Always use the upgradeable versions of OpenZeppelin contracts when working with proxies.
UUPS proxies
UUPS (Universal Upgradeable Proxy Standard) stores upgrade logic in the implementation contract via the upgradeTo function.
UUPS ERC20 example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract UUPSToken is
ERC20Upgradeable,
OwnableUpgradeable,
UUPSUpgradeable
{
function initialize(address initialOwner) public initializer {
__ERC20_init("UUPSToken", "UUP");
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
_mint(initialOwner, 1_000_000 ether);
}
function _authorizeUpgrade(address newImplementation)
internal
override
onlyOwner
{}
}
Deployment is usually done with Hardhat Upgrades plugin or OpenZeppelin Defender, which deploys the proxy and implementation and wires them together.
Transparent proxies
Transparent proxies separate the admin (who can upgrade) from users (who interact with the implementation). OpenZeppelin provides proxy contracts and helper tooling, especially in combination with Hardhat.
In code, you still write an upgradeable implementation (with initializers), but the proxy admin logic is handled by the proxy contract and upgrade tooling.
UUPS offers more flexibility and slightly lower gas on upgrades, while Transparent proxies are conceptually simpler to reason about for some teams. Both are widely used in production.
Initializers & storage gaps
Upgradeable contracts cannot use Solidity constructors. Instead, they use initializer functions, usually called initialize.
Initializers
function initialize(address owner) public initializer {
__ERC20_init("MyToken", "MTK");
__Ownable_init(owner);
}
The initializer modifier ensures it can only be called once.
Storage gaps
Upgradeable base contracts in OpenZeppelin often include a reserved storage gap:
uint256[50] private __gap;
This is used to avoid storage layout collisions if new state variables are added in future versions. In your own upgradeable contracts, avoid reordering fields and always append new variables at the end.
Security guidelines
OpenZeppelin helps, but security is still your responsibility. Some basic guidelines:
- Use audited base contracts (OpenZeppelin) instead of homegrown implementations.
- Prefer Ownable or AccessControl instead of custom permission schemes.
- Protect external calls with ReentrancyGuard where applicable.
- Add Pausable to critical flows so you can react to incidents.
- Use SafeERC20 when interacting with tokens.
- Write thorough tests for role restrictions and failure paths.
Testing OpenZeppelin with Hardhat
Testing OZ-based contracts in Hardhat is just like testing any other contract, but you can focus on your custom logic and role configuration instead of basic token behavior.
Example ERC20 test (TypeScript)
// test/MyToken.ts
import { expect } from "chai";
import { ethers } from "hardhat";
describe("MyToken", () => {
it("mints initial supply to deployer", async () => {
const [deployer] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy();
const total = await token.totalSupply();
const balance = await token.balanceOf(deployer.address);
expect(balance).to.equal(total);
});
it("transfers tokens", async () => {
const [deployer, user] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
const token = await Token.deploy();
await token.transfer(user.address, 100n);
expect(await token.balanceOf(user.address)).to.equal(100n);
});
});
Testing OpenZeppelin with Foundry
Foundry is great for testing OZ-based protocols using fuzzing, cheatcodes, and invariants.
Example test with forge-std
// test/MyToken.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/MyToken.sol";
contract MyTokenTest is Test {
MyToken internal token;
address internal owner = address(0xABCD);
address internal user = address(0xBEEF);
function setUp() public {
vm.prank(owner);
token = new MyToken();
}
function testInitialSupplyAssignedToOwner() public {
assertEq(token.balanceOf(owner), token.totalSupply());
}
function testTransfer() public {
vm.prank(owner);
token.transfer(user, 100 ether);
assertEq(token.balanceOf(user), 100 ether);
}
}
Common pitfalls
Some frequent mistakes when using OpenZeppelin:
- Modifying base contracts directly instead of inheriting and extending them.
- Mixing regular and upgradeable contracts incorrectly (e.g. using non-upgradeable ERC20 in a proxy setup).
- Forgetting to restrict sensitive functions with onlyOwner or onlyRole.
- Not thinking about how ownership or roles will be transferred (to multisig / DAO / timelock).
- Not adding a pause mechanism for highly critical protocols.
OpenZeppelin Defender
OpenZeppelin Defender is a hosted platform for managing smart contract operations securely. It includes relayers, admin actions, scheduled tasks, upgrade flows, and more.
- Use Defender Admin to manage upgrades and role changes via a secure UI.
- Use Relayers to send transactions from backend services without exposing keys.
- Connect Defender with multisig wallets for approvals.
While Defender is optional, it pairs well with OpenZeppelin Contracts and upgradeable patterns.
Integrating OpenZeppelin into a Web3 stack
In a modern stack, OpenZeppelin usually sits at the core of your Solidity code, with other tools around it:
- Use Hardhat or Foundry for builds, tests, and scripts.
- Use OpenZeppelin Contracts for ERC standards and access control.
- Use OpenZeppelin Upgrades and/or Defender for proxy management.
- Use frontends (React / Next.js / Viem / Wagmi) to talk to your OZ-based contracts.
Your Web3 Studio site can link all these together: Hardhat page for workflows, Foundry page for testing, and this OpenZeppelin page for contract-level building blocks.
Resources
- Official OpenZeppelin Contracts documentation.
- Contracts Wizard for generating standard token and access control contracts.
- OpenZeppelin Upgrades plugins for Hardhat and Foundry integration guides.
- Security guidelines and best practices articles from OpenZeppelin.
- Public audits that show real-world usage of OpenZeppelin Contracts.