{"id":"ERC-721","name":"Non-Fungible Token Standard","status":"Final","chain":"both","category":{"id":"nft","name":"NFT Capabilities","description":"What NFTs can do beyond basic ownership"},"journeyStages":[{"id":"asset-display","name":"Asset Discovery & Display","description":"Seeing tokens, NFTs, and balances"},{"id":"approving","name":"Approving & Permissioning","description":"Granting permissions for actions"},{"id":"executing","name":"Executing Transactions","description":"Performing on-chain actions"}],"uxImpact":"ERC-721 defines non-fungible tokens (NFTs). Unlike ERC-20 where every token is identical, each ERC-721 token has a unique ID. This enables digital art, collectibles, game items, membership passes, and anything where each item is distinct. It specifies ownership, transfers, approvals, and an optional metadata extension — the foundation of the NFT ecosystem.","officialUrl":"https://ercs.ethereum.org/ERCS/erc-721","lastReviewed":"2026-09-18","lastUpdated":"2026-09-18","kind":"proposal","hasDetailedContent":true,"content":{"id":"ERC-721","summary":"ERC-721 defines non-fungible tokens (NFTs). Unlike ERC-20 where every token is identical, each ERC-721 token has a unique ID. This enables digital art, collectibles, game items, membership passes, and anything where each item is distinct. It specifies ownership, transfers, approvals, and an optional metadata extension — the foundation of the NFT ecosystem.","designerTakeaways":["You can preview image, collection name, and token id before irreversible transfers.","You can use per-token approval unless marketplace trust justifies operator approve.","You can lazy-load metadata with placeholders when images or traits fail to load."],"applicability":{"whenToUse":["Each asset has a unique id and separate ownership.","Marketplaces or wallets must list, transfer, or approve single items.","An optional tokenURI can link each token to metadata; treat fields such as images and traits as optional."],"whenToAvoid":["Large quantities of identical items (prefer ERC-1155).","Fungible positions or vault shares (use ERC-20 or ERC-4626).","You only need off-chain collectibles with no wallet transfers."]},"designDecisions":[{"question":"Single-token approve or operator approve?","recommendation":"Use setApprovalForAll only with marketplace trust copy; default to per-token approve for unknown spenders.","rationale":"Operator approval covers this holder’s tokens in this collection, including later holdings until revoked; it is broader than one listing."},{"question":"How is metadata failure shown?","recommendation":"Placeholder art plus \"metadata unavailable\" instead of a broken image tile.","rationale":"Broken media reads as a scam or bug during high-intent purchase moments."},{"question":"What does the transfer confirmation show?","recommendation":"Preview image, collection name, token id, and recipient before sign.","rationale":"NFT transfers are irreversible; users need identity checks beyond a hex address."},{"question":"How do you surface collection-level risk?","recommendation":"Flag unverified collections and spoofed names separately from verified badges.","rationale":"Users confuse lookalike collections when browsing quickly."}],"statesToDesign":[{"state":"Metadata loading","trigger":"tokenURI fetch or image CDN request pending.","userNeed":"Know the item exists while media loads.","designResponse":"Skeleton tile with collection name and token id visible."},{"state":"Action authorization","trigger":"User opens or attempts an action while current ownership or operator authorization is absent or unknown.","userNeed":"Understand which actions are available and why an action is unavailable.","designResponse":"Check ownerOf, getApproved(tokenId), and isApprovedForAll against the current holder and marketplace policy; show Buy when a listing permits it, and disable transfer or listing only when authorization is absent."},{"state":"Approval for marketplace","trigger":"Marketplace needs operator or token approval.","userNeed":"Know scope of access being granted.","designResponse":"State whether approval concerns one token or all tokens held by this owner in this collection; name the approved address."},{"state":"Transfer in progress","trigger":"safeTransferFrom submitted.","userNeed":"Confidence the item is leaving the wallet.","designResponse":"Pending state on item card; remove or dim after confirmation."},{"state":"Received airdrop","trigger":"Unexpected token appears in wallet.","userNeed":"Avoid interacting with malicious airdrops.","designResponse":"Hide or quarantine unverified drops; warn before first interaction."}],"problemsSolved":[{"problem":"No standard for unique digital items","oldWay":"Each project invented its own ownership system","newWay":"ERC-721: universal interface for unique tokens","impact":"critical"},{"problem":"Marketplaces couldn't support all NFTs","oldWay":"Custom integration for each NFT contract","newWay":"ERC-721 offers a common integration surface; venue indexing, listing, media support, and policy still depend on the marketplace and collection.","impact":"critical"},{"problem":"No standard way to link NFT to its image/metadata","oldWay":"Random ways to store and retrieve NFT info","newWay":"tokenURI() may return an optional metadata URI; fields such as image and attributes are optional and untrusted","impact":"high"},{"problem":"Couldn't approve operators for all NFTs at once","oldWay":"Approve each NFT individually before selling","newWay":"setApprovalForAll() for marketplace integration","impact":"high"}],"uxPatterns":[{"name":"NFT Collection Gallery","description":"Display user's NFT holdings in a visual grid","mockup":"concept/nft-gallery","userFlow":["App queries user's NFT holdings","Fetches tokenURI for each","Loads and caches images","Displays in responsive grid","Lazy loads as user scrolls"]},{"name":"NFT Detail View","description":"Single NFT with full metadata and actions","userFlow":["User selects NFT from gallery","App fetches tokenURI metadata","Reads available JSON fields, including optional attributes","Shows available fields and rarity only when trusted collection data supports it","Displays action buttons"]},{"name":"NFT Transfer","description":"Send NFT to another address","userFlow":["User selects NFT to transfer","Enters recipient address","App validates address format","Shows value and warning","User confirms transfer","Calls safeTransferFrom()"]},{"name":"Marketplace Approval","description":"Review a marketplace’s requested token or operator approval separately from its listing policy.","userFlow":["User tries to list NFT","Marketplace detects no approval","Shows approval options","User selects scope","Calls approve() or setApprovalForAll()","Continue the listing flow only when the marketplace’s authorization and listing policy permit it"]}],"uiComponents":[{"name":"NFTCard","description":"Thumbnail card showing NFT image, name, collection","states":["loading","loaded","error","selected"],"props":["tokenId","contractAddress","metadata","onClick"]},{"name":"NFTImage","description":"Handles various media types (image, video, audio, 3D)","states":["loading","loaded","error","fallback"],"props":["uri","type","fallbackImage"]},{"name":"AttributeList","description":"Displays NFT traits with rarity percentages","states":["loading","loaded","no-attributes"],"props":["attributes[]","showRarity"]},{"name":"CollectionBadge","description":"Shows collection name with verified checkmark","states":["verified","unverified","flagged"],"props":["name","address","isVerified"]},{"name":"NFTTransferForm","description":"Form for transferring NFT to another address","states":["input","validating","ready","sending","success"],"props":["nft","onTransfer"]}],"antiPatterns":[{"pattern":"Not validating recipient can receive NFTs","why":"Some contracts can't receive NFTs, tokens get stuck","instead":"Prefer safeTransferFrom() for its onERC721Received callback check when the destination has code. Verify the intended recipient and custody policy too; callback acceptance does not guarantee that the recipient can return the NFT.","severity":"critical"},{"pattern":"Loading all NFT images at once","why":"Large collections crash browser, waste bandwidth","instead":"Virtualize list, lazy load images as they scroll into view","severity":"high"},{"pattern":"Trusting tokenURI without validation","why":"Malicious metadata can include XSS, phishing links","instead":"Sanitize metadata, validate URLs, sandbox iframes","severity":"critical"},{"pattern":"Not caching metadata","why":"Every view re-fetches from IPFS/HTTP, slow and wasteful","instead":"Cache metadata with a refresh policy; handle applicable ERC-4906 events, known URI changes, time-based expiry and manual refresh rather than relying only on transfers.","severity":"medium"},{"pattern":"Assuming all NFTs have metadata","why":"Some contracts return empty tokenURI or fail","instead":"Graceful fallback: show token ID, placeholder image","severity":"high"},{"pattern":"Not explaining setApprovalForAll risks","why":"Users don't realize they're approving ALL NFTs in collection","instead":"Clear warning: \"This approves ALL your [collection] NFTs\"","severity":"high"}],"keyTakeaways":["ERC-721 = unique tokens, each with distinct ID","Prefer safeTransferFrom() for the receiver callback check; acceptance does not guarantee custody safety or recovery","Lazy load images, cache metadata","Sanitize tokenURI metadata for security","Explain setApprovalForAll risks clearly"],"technicalNotes":"ERC-721 specifies: balanceOf, ownerOf, safeTransferFrom, transferFrom, approve, setApprovalForAll, getApproved, and isApprovedForAll. Events: Transfer, Approval, ApprovalForAll. The name(), symbol(), and tokenURI() metadata extension is optional; if tokenURI() is present, its URI and JSON fields such as name, description, image, and attributes are optional and untrusted. safeTransferFrom checks if the recipient is a contract and calls onERC721Received().","specConstraints":[{"id":"unique-token-identity","constraint":"Each NFT is distinguishable and its ownership must be tracked separately and atomically.","appliesTo":"application","normativity":"rationale","specQuote":"NFTs are *distinguishable* and you must track the ownership of each one separately.","specSection":"Abstract","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#abstract","designImplication":"Use a token-specific ownership and activity view rather than aggregating distinct NFTs as interchangeable units."},{"id":"erc721-interface-support","constraint":"A compliant ERC-721 contract must implement the ERC721 and ERC165 interfaces.","appliesTo":"account-implementation","normativity":"normative","specQuote":"Every ERC-721 compliant contract must implement the `ERC721` and `ERC165` interfaces","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#specification","designImplication":"Feature-detect the core NFT interface before presenting ownership, transfer, approval, or metadata actions."},{"id":"operator-multiplicity","constraint":"The contract must allow more than one operator per owner for ApprovalForAll management.","appliesTo":"application","normativity":"normative","specQuote":"/// @dev Emits the ApprovalForAll event. The contract MUST allow\n    ///  multiple operators per owner.","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#specification","designImplication":"Show operator approvals as a list or independently managed permissions, and make revocation of one operator separate from other operators."},{"id":"safe-transfer-wallet-interface","constraint":"Wallets or broker applications accepting safe transfers must implement the receiver interface.","appliesTo":"application","normativity":"normative","specQuote":"A wallet/broker/auction application MUST implement the **wallet interface** if it will accept safe transfers.","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#specification","designImplication":"Keep safe-transfer acceptance explicit and explain when a recipient contract cannot receive NFTs."},{"id":"safe-transfer-magic-return","constraint":"A safe-transfer receiver that returns anything other than the specified magic value must cause the transaction to revert.","appliesTo":"application","normativity":"normative","specQuote":"///  transfer. Return of other than the magic value MUST result in the\n    ///  transaction being reverted.","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#specification","designImplication":"Show the transfer as failed when receiver acceptance is invalid, and do not mark custody as complete on a rejected callback."},{"id":"token-identity","constraint":"A tokenId is stable for the contract's life, the contract-address/tokenId pair identifies the asset, and callers must not infer a numbering pattern.","appliesTo":"application","normativity":"rationale","specQuote":"Every NFT is identified by a unique `uint256` ID inside the ERC-721 smart contract. This identifying number SHALL NOT change for the life of the contract. The pair `(contract address, uint256 tokenId)` will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a \"black box\".","specSection":"Rationale / NFT Identifiers","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#nft-identifiers","designImplication":"Use the contract address and tokenId as the asset key, preserve it across views and links, and avoid implying that token IDs are sequential, sortable, or human-meaningful."},{"id":"transfer-failure-conditions","constraint":"Implementations may reject transfers in additional documented situations beyond the standard's required throw conditions.","appliesTo":"application","normativity":"rationale","specQuote":"The transfer and accept functions' documentation only specify conditions when the transaction MUST throw. Your implementation MAY also throw in other situations.","specSection":"Rationale / Transfer Mechanism","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#transfer-mechanism","designImplication":"Make pause, blocklist, unsafe-recipient, fee, or other implementation-specific transfer restrictions legible before confirmation and do not promise that every otherwise-authorized transfer will succeed."},{"id":"erc20-semantics","constraint":"ERC-721 adopts balanceOf, totalSupply, name, and symbol semantics from ERC-20 without requiring the decimals function.","appliesTo":"application","normativity":"backwards-compatibility","specQuote":"We have adopted `balanceOf`, `totalSupply`, `name` and `symbol` semantics from the ERC-20 specification.","specSection":"Backwards Compatibility","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#backwards-compatibility","designImplication":"Reuse familiar collection and ownership labels where helpful, but do not assume ERC-20 fungibility or require decimals for an ERC-721 integration."},{"id":"optional-metadata-extension","constraint":"The ERC-721 metadata extension is OPTIONAL for smart contracts.","appliesTo":"application","normativity":"normative","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#specification","designImplication":"Feature-detect metadata before showing names or token details, and provide an explicit unavailable state when the extension is absent.","specQuote":"The **metadata extension** is OPTIONAL for ERC-721 smart contracts (see \"caveats\", below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent."},{"id":"optional-enumeration-extension","constraint":"The ERC-721 enumeration extension is OPTIONAL for smart contracts.","appliesTo":"application","normativity":"normative","specSection":"Specification","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#specification","designImplication":"Feature-detect enumeration before promising complete NFT discovery or collection counts.","specQuote":"The **enumeration extension** is OPTIONAL for ERC-721 smart contracts (see \"caveats\", below). This allows your contract to publish its full list of NFTs and make them discoverable."},{"id":"token-invalidity","constraint":"NFTs MAY become invalid or be destroyed while their unique identifiers remain part of the contract's identity model.","appliesTo":"application","normativity":"rationale","specSection":"Rationale > NFT Identifiers","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#nft-identifiers","designImplication":"Keep token identity stable in links and history while showing an explicit invalid, burned, or unavailable state instead of deleting the asset from the record.","specQuote":"Every NFT is identified by a unique `uint256` ID inside the ERC-721 smart contract. This identifying number SHALL NOT change for the life of the contract. The pair `(contract address, uint256 tokenId)` will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a \"black box\". Also note that NFTs MAY become invalid (be destroyed). Please see the enumeration functions for a supported enumeration interface."},{"id":"nft-approval-flow","constraint":"An authorized operator MAY set the approved address for an NFT.","appliesTo":"application","normativity":"rationale","specSection":"Rationale > Transfer Mechanism","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#transfer-mechanism","designImplication":"Show approved-address and operator authority separately, including the revoke path, before a wallet or marketplace action.","specQuote":"Additionally, an authorized operator may set the approved address for an NFT. This provides a powerful set of tools for wallet, broker and auction applications to quickly use a *large* number of NFTs."},{"id":"metadata-uri-mutability","constraint":"An NFT's URI MAY be mutable and can change over time.","appliesTo":"application","normativity":"rationale","specSection":"Rationale > Metadata","specUrl":"https://ercs.ethereum.org/ERCS/erc-721#metadata","designImplication":"Label metadata freshness and update provenance; do not imply that a previously rendered image or description is immutable.","specQuote":"A mechanism is provided to associate NFTs with URIs. We expect that many implementations will take advantage of this to provide metadata for each NFT. The image size recommendation is taken from Instagram, they probably know much about image usability. The URI MAY be mutable (i.e. it changes from time to time). We considered an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can naturally change."}],"lastUpdated":"2026-09-18","sources":[{"label":"ERC-721 official specification","url":"https://ercs.ethereum.org/ERCS/erc-721","type":"official-spec"}],"lastReviewed":"2026-09-18"},"sources":[{"label":"Official specification","url":"https://ercs.ethereum.org/ERCS/erc-721","type":"official-spec"},{"label":"Discussion search","url":"https://ethereum-magicians.org/search?q=ERC-721","type":"discussion"}],"urls":{"canonical":"https://www.eipsfordesigners.com/standards/ERC-721","markdown":"https://www.eipsfordesigners.com/standards/ERC-721/content.md","agent":"https://www.eipsfordesigners.com/standards/ERC-721/agent.md","api":"https://www.eipsfordesigners.com/api/standards/ERC-721","official":"https://ercs.ethereum.org/ERCS/erc-721","discussion":"https://ethereum-magicians.org/search?q=ERC-721"},"freshness":{"lastReviewed":"2026-09-18","lastUpdated":"2026-09-18"}}