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 method | URI example | Notes |
|---|---|---|
| Hosted JSON | https://example.com/metadata/1.json | Host the JSON on your own server or cloud storage. |
| IPFS | ipfs://bafy... | Return an ipfs:// URI for metadata stored on IPFS. |
| Arweave | ar://example-transaction-id | Return an ar:// URI for metadata stored on Arweave. |
| Data URI | data:application/json;base64,... | Return Base64-encoded JSON directly from the contract. |
| ERC-4804 | web3://example.eth/tokenJSON/1 | Resolve 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
web3:// URIsOpenSea 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.
Updated about 4 hours ago
