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.

Target audience: developers building tokens, NFTs, protocols and upgradeable systems using Solidity ≥ 0.8.x.

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.
Don’t reimplement standards

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.
Use Wizard, then refactor

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:

  1. Install @openzeppelin/contracts (and optionally -upgradeable).
  2. Pick a base contract (e.g. ERC20, ERC721).
  3. Inherit from it and implement constructor / initializer with your parameters.
  4. Add access control (owner, roles, or both).
  5. 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);
    }
}
Use SafeERC20 in other contracts

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.
Enumerable trade-offs

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.
Upgradeable contracts require discipline

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.

Which pattern to choose?

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.