Open-source project
OpenZeppelin/openzeppelin-upgrades avatar
OpenZeppelin/openzeppelin-upgrades

OpenZeppelin Upgrades plugins: putting proxy deployments and upgrades behind an API

Plugins for Hardhat and Foundry to deploy and manage upgradeable contracts on Ethereum.

660 stars286 forksTypeScriptMIT

At a glance

What is it?
The OpenZeppelin Upgrades plugins add upgradeable contract support to Hardhat and Foundry, covering proxy deployment, upgrade safety validation, proxy admin ownership and beacon proxies. The core value is not the proxy pattern itself but the checks that run before a deployment reaches the chain, and the Hardhat and Foundry plugins differ sharply in how much state they keep.
Who is it for?
The Upgrades plugins suit a Solidity team that wants upgradeable contracts without hand-writing proxy wiring, because deployProxy and upgradeProxy perform upgrade safety and compatibility validation before anything is sent to the chain, and the three proxy patterns are covered by one set of calls.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The point of the plugins is the validation, not the proxy

The plugins provide functions which take care of managing upgradeable deployments of your contracts. That phrasing understates what changes for a developer.

The pattern itself is well known. An implementation contract holds the logic, a proxy contract holds the storage and delegates calls to the implementation, and an upgrade swaps the implementation the proxy points at. The hard part is not writing that, it is knowing whether the swap is safe.

So the first thing deployProxy does is validate that the implementation is upgrade safe. The second step is deploying the implementation contract, and the third is creating and initializing the proxy contract, along with a proxy admin if needed.

The upgrade path mirrors it with one extra check. When you call upgradeProxy, the plugin validates that the new implementation is both upgrade safe and compatible with the previous one, then deploys the new implementation contract, then upgrades the proxy to use it.

That compatibility check is the part with real consequences. A proxy's storage layout is fixed at deployment. If the new implementation declares its variables in a different order, or removes a variable that the old one had, the existing state in that slot gets reinterpreted. The contract keeps running and the data is silently wrong. A plugin that refuses to deploy an incompatible implementation before it reaches the chain turns a class of bug that takes an audit to find into an error you see in a test.

The Hardhat plugin also checks if there is an implementation contract deployed with the same bytecode and skips the deployment if one already exists. That detail matters for the deployment cost of every version of every contract, and it means a redeploy of identical code does not create a new address.

The primary language is TypeScript, the licence is MIT, and the project is under the OpenZeppelin organisation with documentation at docs.openzeppelin.com/upgrades-plugins. The last push was on 2026-10-18, and the most recent release tags are @openzeppelin/hardhat-upgrades at 4.1.0 from 2026-07-23 and @openzeppelin/upgrades-core at 1.46.0 from 2026-06-02.

Hardhat keeps a manifest, Foundry does not

The two plugins are not the same tool with two front ends, and the README is direct about the difference.

The Hardhat plugin keeps track of all the implementation contracts you have deployed in an .openzeppelin folder in the project root, with one file per network. The recommendation is to commit to source control the files for all networks except the development ones, which you may see as .openzeppelin/unknown-*.json.

So on Hardhat the deployment history is a file in your repository. That file is what lets the plugin know which implementation a given proxy currently points at, and it is what lets upgradeProxy perform the compatibility check against a previous version rather than against nothing. It also means the state of your deployed system lives in version control, which is a design choice with real operational consequences: it must be committed, it can conflict in a branch, and it is a file that leaks deployed addresses.

The Foundry plugin does not keep track of implementation contracts. Instead it requires you to define reference contracts in order to validate new versions of implementations for upgrade safety.

That is a meaningfully different model. The reference contract is the old implementation, held in your source tree and pinned, and the check compares the new implementation against it. Nothing is written to disk by the plugin; the history is in your code, in the version control you already have.

The trade-off follows directly. The Hardhat approach needs no extra declarations but needs the manifest committed and kept in step with the chain. The Foundry approach needs you to declare and maintain the reference contract but keeps everything reviewable in source.

The Foundry plugin lives in a separate repository, openzeppelin-foundry-upgrades, so the two have independent release lines. That matters when pinning: the Hardhat plugin version and the Foundry plugin version are not synchronised with each other.

Three proxy patterns with different upgrade mechanics

The plugins support the UUPS, transparent, and beacon proxy patterns, and the difference between them is not cosmetic because it decides who can upgrade what.

UUPS and transparent proxies are upgraded individually, whereas any number of beacon proxies can be upgraded atomically at the same time by upgrading the beacon that they point to.

The call names differ accordingly. For UUPS and transparent proxies you use deployProxy and upgradeProxy. For beacon proxies you use deployBeacon, deployBeaconProxy, and upgradeBeacon.

That atomic property is the reason to reach for beacons. If you have twenty proxies that must all move together, a beacon gives you one upgrade that changes all twenty at once, and you cannot end up with half of them on the new implementation. Individual proxies do not give you that, and a script that upgrades twenty of them one at a time has twenty chances to stop halfway.

On the admin side, transparent proxies have an admin address with the rights to upgrade them. By default the admin is a proxy admin contract deployed behind the scenes. The restriction on that admin is worth internalising: the admin of a proxy can only upgrade it, but not interact with the implementation contract. The admin cannot call your implementation's functions, so an admin key cannot drain the proxy's balance by calling a withdrawal function directly.

UUPS and beacon proxies do not use admin addresses at all. UUPS proxies rely on an _authorizeUpgrade function to be overridden to include access restriction to the upgrade mechanism, and beacon proxies are upgradable only by the owner of their corresponding beacon.

So UUPS pushes the access control into your own contract, where you write it and can read it, while transparent delegates it to a separate deployed contract. For a team that wants the upgrade right to be visibly constrained by code they own, UUPS is the more direct expression of that.

Owning the admin, and the ProxyAdmin reuse trap

The proxy admin contract also defines an owner address with the rights to operate it. By default the proxy admin's owner is the initialOwner address used during deployment of the transparent proxy if provided, otherwise it is the externally owned account used during deployment.

Ownership can be moved later. In the Hardhat plugin you call the admin.transferProxyAdminOwnership function, and in Foundry you use the transferOwnership function of the proxy admin contract.

The warning in the README is the single most operationally dangerous item in the whole document, and it is a WARNING block for a reason.

Do not reuse an already deployed ProxyAdmin. Before @openzeppelin/contracts version 5.x, the address provided to transparent proxies was an initialAdmin as opposed to an initialOwner of a newly deployed ProxyAdmin. Reusing a ProxyAdmin will disable upgradeability in your contract.

The mechanism behind that is worth understanding, because the symptom would otherwise be baffling. The semantics of that address changed between contract library versions. An address that used to be interpreted as the admin of a transparent proxy is now interpreted as the owner of a separately deployed ProxyAdmin contract. Pass the same address under the new meaning and the authority you intended to delegate goes to a contract that has no relationship to your proxy's upgrade right. The result is that your proxy cannot be upgraded at all, and the transaction that caused it succeeded.

The warning also flags the second-order risk: this may cause issues with multiple proxy deployments. Where a deployment script creates several proxies pointing at one shared admin address, the old and new semantics apply inconsistently across them, and you end up with some proxies upgradable and some not.

The other half of the ownership story is that once you have transferred the rights to upgrade a proxy or beacon to another address, you can still use your local setup to validate and deploy the implementation contract. The prepareUpgrade function validates that the new implementation is upgrade-safe and compatible with the previous one, and deploys it using your local Ethereum account. You then execute the upgrade itself from the admin or owner address. Separating who validates from who signs is a sensible pattern for a multisig-controlled system.

Defender is the alternative path, and it changes who holds the keys

You can also use the defender.proposeUpgrade or defender.proposeUpgradeWithApproval functions to automatically set up the upgrade in OpenZeppelin Defender.

This is a genuinely different approach from the local one, and it is worth being clear about the difference rather than treating it as a convenience feature.

In the local flow, the plugin validates, deploys the implementation using your local Ethereum account, and the upgrade transaction is executed from the admin or owner address. Your key signs it. The plugin is a script on your machine.

In the Defender flow, the upgrade is set up in Defender instead, which means the operation is staged and carried out through Defender's own infrastructure rather than from your local account. The proposeUpgrade and proposeUpgradeWithApproval names describe the shape: you propose, and in the second case approval is involved before the upgrade goes through.

That difference matters in two directions. It is the right approach when the upgrade authority is meant to sit behind a multisig or a policy-controlled service, because the local account is not the one whose approval counts. It is the wrong approach when you want to keep the operation entirely inside your own infrastructure and your own keys, because the staged upgrade then depends on a hosted service being reachable and behaving as expected at the moment you need it.

The same distinction applies to the ownership model generally. The README's framing of transferring upgrade rights to another address and then using prepareUpgrade locally is a way of keeping validation in your toolchain while the authority sits elsewhere, and the Defender functions are the version of that where the staging also moves off your machine.

For a team that already runs Defender for other reasons, the plugins integrate with it. For a team that does not, the local flow needs nothing beyond a funded account, and adding Defender to the dependency list for the sake of an upgrade is a heavier decision than it looks.

Where the Upgrades plugins are the wrong tool

The first wrong-tool case is a project that does not want proxies at all. Every function here assumes a proxy with fixed storage and a separate implementation behind it. If the upgrade model you need is different, for example replacing a contract's logic by deploying a new contract and migrating state yourself, then the compatibility validation and the proxy admin ownership model are solving a problem you do not have, and the library's constraints are in the way rather than helpful.

The second case is a contract whose storage layout genuinely must change. The compatibility check is a feature when you are fixing a bug in a new version of the same contract, and a blocker when the upgrade is a redesign. There is no flag to skip it that the README describes, so the tool's answer to a redesign is that you are using the wrong upgrade mechanism.

The third case is a team that will not maintain a deployment manifest. The Hardhat plugin's model requires committing .openzeppelin files for every non-development network and keeping them accurate. A team that does not want deployed addresses in version control, or that deploys from CI with an ephemeral checkout, has to solve that problem itself, and the compatibility check depends on the answer being right.

The fourth case is a deployment that happens without the plugin in the loop. The upgrade safety validation only runs when the plugin performs the deployment. A contract deployed by some other script or by hand, then later upgraded through the plugin, has no recorded previous implementation for the Hardhat plugin to compare against, which is exactly why the Foundry plugin asks you to declare reference contracts explicitly. Both plugins assume the tool was in the loop from the start.

There is also a repository-level signal about how the project is organised. It is a yarn workspaces monorepo with the packages under packages/, using changesets for versioning and changelog generation, wsrun to fan out scripts across packages, eslint with max-warnings 0, nyc for coverage and TypeScript project builds through tsc -b. The AGENTS.md and CONSTITUTION.md files at the root suggest the project is being developed with some level of process discipline. The last push was on 2026-10-18, so the code is current even though the plugin release tags in the list are from the middle of 2026.

Editorial conclusion

The Upgrades plugins suit a Solidity team that wants upgradeable contracts without hand-writing proxy wiring, because deployProxy and upgradeProxy perform upgrade safety and compatibility validation before anything is sent to the chain, and the three proxy patterns are covered by one set of calls. It does not suit a project that wants a non-proxy upgrade model, since storage layout compatibility is assumed throughout, and it does not suit a team that must not depend on OpenZeppelin Defenders infrastructure if they intend to use the proposeUpgrade path. Before adopting, run the plugins against a local network with a contract you plan to change later, read the warning about not reusing a deployed ProxyAdmin, and commit the .openzeppelin files for every network except the development ones.

Frequently asked questions

What do the OpenZeppelin Upgrades plugins do?

They are plugins for Hardhat and Foundry that deploy and manage upgradeable contracts on Ethereum. They provide functions for deploying upgradeable contracts, upgrading deployed contracts, managing proxy admin rights, and using the same setup in tests.

How do I install the OpenZeppelin Upgrades plugins?

The README does not give the install command itself, it points to the per-plugin documentation instead: packages/plugin-hardhat/README.md for the Hardhat plugin and the openzeppelin-foundry-upgrades repository for the Foundry plugin. The published packages are @openzeppelin/hardhat-upgrades and @openzeppelin/upgrades-core.

Which proxy patterns do the OpenZeppelin Upgrades plugins support?

UUPS, transparent and beacon proxies. UUPS and transparent proxies are upgraded individually with deployProxy and upgradeProxy, while any number of beacon proxies can be upgraded atomically by upgrading the beacon, using deployBeacon, deployBeaconProxy and upgradeBeacon.

What is the difference between the Hardhat and Foundry versions of the Upgrades plugins?

The Hardhat plugin keeps track of deployed implementation contracts in an .openzeppelin folder in the project root with one file per network, and it is advised to commit those files for all networks except development ones. The Foundry plugin does not keep that state and instead requires you to define reference contracts so new implementations can be validated for upgrade safety.

Why should I not reuse a deployed ProxyAdmin?

Before @openzeppelin/contracts version 5.x, the address given to transparent proxies was an initialAdmin, whereas it is now an initialOwner of a newly deployed ProxyAdmin. Reusing a ProxyAdmin under the new meaning disables upgradeability in your contract, and across several proxies sharing one admin address it can leave some upgradable and others not.

What license is the OpenZeppelin Upgrades plugins released under?

The OpenZeppelin Upgrade plugins are released under the MIT License, with the LICENSE file at the root of the repository. The project is a yarn workspaces monorepo with the packages under packages/.

Official sources

  1. License: MIT
  2. OpenZeppelin/openzeppelin-upgrades on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openzeppelin-openzeppelin-upgrades.svg)](https://hysenlabs.com/projects/openzeppelin-openzeppelin-upgrades)

Was this useful?