Metadata storage

Choose where token metadata lives and how your contract returns it

Your token URI can return metadata from a hosted URL, a decentralized network, or the contract itself.

Storage methodURI exampleNotes
Hosted JSONhttps://example.com/metadata/1.jsonHost the JSON on your own server or cloud storage.
IPFSipfs://bafy...Return an ipfs:// URI for metadata stored on IPFS.
Arweavear://example-transaction-idReturn an ar:// URI for metadata stored on Arweave.
Data URIdata:application/json;base64,...Return Base64-encoded JSON directly from the contract.
ERC-4804web3://example.eth/tokenJSON/1Resolve JSON through an EVM call.

IPFS and Arweave

For IPFS, return ipfs://<hash>, such as ipfs://QmTy8w65yBXgyfG2ZBg5TrfB2hPjrDQH3RCQFJGkARStJb.

For Arweave, return ar://<hash>, such as ar://jK9sR4OrYvODj7PD3czIAyNJalub0-vdV_JAg1NqQ-o.

Onchain metadata

For small collections or generative art, tokenURI or uri can return Base64-encoded JSON with a data:application/json;base64, prefix.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract OnchainMetadataExample is ERC721 {
    constructor() ERC721("Onchain Creature", "OCC") {
        _mint(msg.sender, 1);
    }

    function tokenURI(uint256 tokenId) public pure override returns (string memory) {
        require(tokenId == 1, "Nonexistent token");

        string memory json = Base64.encode(
            bytes("{\"name\":\"Onchain Creature #1\",\"description\":\"A fully onchain NFT stored in Base64 JSON.\"}")
        );

        return string.concat("data:application/json;base64,", json);
    }
}

Onchain metadata increases gas costs. Use it when the metadata is compact.

ERC-4804 web3:// URIs

OpenSea supports ERC-4804 web3:// URIs for NFT metadata. A web3:// URI can resolve an ENS name or contract address, an optional chain ID, a method call, and its arguments.

Return raw JSON from the target method when possible:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract MetadataResolver {
    function tokenJSON(uint256) external pure returns (string memory) {
        return "{\"name\":\"My NFT\",\"description\":\"...\",\"image\":\"...\"}";
    }
}

For example, web3://test4804nftsupport.eth/tokenJSON/0 resolves the ENS name on mainnet, calls tokenJSON(uint256) with 0, and parses the returned JSON. To call a contract on another chain, include its chain ID: web3://0xd4c5292b9689238f0a51c88505b1d1d6714ce95a0:8453/tokenURI/1.

OpenSea also supports a data URI returned from a web3:// endpoint for backward compatibility. Return raw JSON for new implementations because it avoids encoding one URI inside another.