Final ERC standard. Extends ERC-721 with two events — `MetadataUpdate(uint256 tokenId)` and `BatchMetadataUpdate(uint256 fromTokenId, uint256 toTokenId)` — so marketplaces and indexers know to refresh `tokenURI` without polling.
- 01dynamic / on-chain generative NFTs
- 02reveal mechanics after mint
- 03evolving game items and SBTs
- 04metadata-as-state collections
- 05any project where `tokenURI` changes after mint
- pnpm add @openzeppelin/contracts
Add ERC-4906 to an ERC-721 by extending `ERC721URIStorage` from `@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol` (which already declares the events) or by emitting them yourself: `emit MetadataUpdate(tokenId)` after any change to a single token's metadata, and `emit BatchMetadataUpdate(fromTokenId, toTokenId)` after a range update. The interface ID is `0x49064906`; advertise it via `supportsInterface`. ERC-4906 layers on top of ERC-721 and ERC-1155 (community-extended) — it never changes transfer semantics, only telegraphs metadata invalidation.
- ⚑Indexers and marketplaces (OpenSea, Reservoir, Alchemy) refresh on a best-effort basis — emitting the event does not guarantee fast or universal refresh.
- ⚑`BatchMetadataUpdate(0, type(uint256).max)` is the convention for 'invalidate everything' but very expensive for indexers — prefer tight ranges where possible.
- ⚑The event must be emitted AFTER the metadata-affecting state change, otherwise refreshers may re-cache stale data.
- ⚑ERC-4906 only signals invalidation — it does not version, sign, or commit to the new metadata. Off-chain `tokenURI` mutability concerns still apply.
- ⚑Some implementations forget to update `supportsInterface` to include `0x49064906`, causing aware indexers to skip the collection entirely.