The term metadata refers to "auxiliary data" that describes some "main data". It's a vague term that developers use for a lot of different things, but it means something specific when talking about NFTs.

    NFT Metadata

    In the case of an NFT, let's say a photograph NFT, the "main data" is the image file. This is likely a binary file format, such as PNG, and is typically stored on some cloud/decentralized server. The metadata is then the "auxiliary data" about the photograph: its title, description, traits, and most importantly, the link to the PNG file wherever it may be stored.

    The metadata is stored in a JSON format, such as this:

    There are several different supported metadata schemas. The one shown above follows one of the formats outlined in the OpenSea Metadata Standards.

    Token URI

    Remember the smart contract function tokenURI from the previous section? The tokenURI function returns the uri of a metadata JSON file like the one shown above! This is how wallets, marketplaces, and other smart contracts know where to find the image for the NFT: they first call the tokenURI function to read the metadata JSON file, and then they display the link from the image field of the JSON.

    For an ERC-721 collection of 10000 NFTs, each token will typically have its own metadata JSON file — this means 10000 separate JSON files. Where do all of these get stored?

    Metadata Storage

    There are typically 3 places where metadata files are stored:

    • "On-chain" - Directly on the blockchain as part of a smart contract
    • IPFS - An immutable, decentralized file storage solution,
    • Custom API - A centralized server that can serve arbitrary data

    Let's walk through each of these options.


    The metadata JSON files can be stored or generated as part of your smart contract. In general, storing data on the blockchain is very expensive, so this approach isn't extremely common. However, for projects where the metadata is relatively simple and can be generated on-the-fly, e.g. Loot, then this approach is a great option.

    The tokenURI function must return a valid uri that represents a JSON file; after generating the metadata fields (title, image, attributes, etc), that data must then be converted into a base64-encoded JSON file.

    As an example, this is what it looks like when calling tokenURI for token 10 of the Loot smart contract on Etherscan.

    Calling tokenURI for Loot token 10

    Decoding this data gives:

    Note that the image uri is also encoded as a base64 SVG file. Even the image is generated as part of the smart contract!


    IPFS is a decentralized storage protocol. It's conceptually similar to consumer storage services like Dropbox or Google Drive, only instead of a single company storing all of the files, anybody can store the files. Storage providers are incentivized to store files because they earn filecoin for their storage space.

    There are a couple features that make IPFS well-suited for storing NFT-related files, compared to e.g. Dropbox:

    • Immutable - Files stored on IPFS can't be changed. An IPFS uri always refers to the exact same file data. Note that files can potentially disappear if there are no providers storing them.
    • Permissionless - Much like the blockchain, IPFS is a permissionless system: anybody can store files and communicate with other peers on the network, and no centralized authority can take files down. (NFT collectors don't have to worry about their NFTs being censored)

    Additionally, IPFS introduces its own ipfs:// uri scheme that's supported by platforms like OpenSea.

    As an example, let's consider Bored Ape Yacht Club again. Calling the tokenURI function for token 10 returns an ipfs:// uri:

    Calling tokenURI for BAYC token 10

    If you put ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/10 in your browser address bar... probably nothing will happen. A few browsers, like Brave, natively support IPFS and will show the file, but most won't. However, the organization behind IPFS hosts a https:// server for accessing files from your browser, in this case:

    Once again we should see a familiar JSON format:

    You'll also notice that this uri ends in the number "10". You can try changing it to any other number 0 through 9999. In order to get the uris to start with the same string, all 10000 metadata files are stored together on IPFS in a single folder.

    For smaller NFT collections, it's also common to upload files individually and use a smart contract that supports setting a completely different uri for each token id.

    There's a great IPFS storage provider,, that's free for the foreseeable future (details on their site)

    Custom API

    Lastly, you can set up a custom API that serves arbitrary JSON metadata. For developers, this is likely the easiest option; for non-developers, IPFS may be simpler.

    The advantage of this approach is speed, cost, and flexibility. As an example, serves a simple set of NFT metadata for testing purposes. For any token id from 0 to infinity, the API will return valid metadata. This would be impossible with IPFS, since you could never upload infinity files.

    For example, token 123456789,

    Choosing a Storage Option

    The following table highlights a few key factors to consider when storing metadata:

    Custom API🛑🛑

    Typically NFT collectors prefer "on-chain" metadata when possible. Data on the blockchain is immutable (can't be changed) and can never disappear, which is exactly what you want for most NFTs. However, this option only really makes sense for certain kinds of very simple NFTs, where the image is embedded into the metadata (e.g. Loot). The image in this case is almost always a small SVG file, rather than PNG files, video files, or interactive multimedia, since otherwise it would simply be too expensive.

    Most projects store metadata files on IPFS. Data stored on IPFS is immutable, but can disappear. Somebody has to pay a storage provider, or run their own server, to keep the files available. Typically today the NFT creator pays for IPFS storage, however conceptually it may make more sense for the collector to pay for storage of the NFTs they buy.

    Custom APIs are simple and inexpensive to set up as a developer. They're also convenient because you can easily fix bugs that you may not encounter until after the NFT sale begins. However, the downside for collectors is that the data is not immutable and can disappear at any time. Many projects still use custom APIs however, and ultimately it's up to the project and its collectors to decide if the fact that data can change or disappear is important or not.

    Arweave is another popular decentralized storage provider besides IPFS. It promises to store files forever for a 1-time payment. It's not as widely supported as IPFS, and it's unclear if this 1-time-payment hypothesis will work long term, but it's definitely an interesting option to consider.

    Configurable Smart Contracts

    Many NFT smart contracts allow configuring uris at the smart contract level, so functionally the NFTs can still change or disappear even if they're stored using an immutable storage system like IPFS.

    This configuration feature is useful for a few reasons:

    • There may be a bug in the metadata, and so it can be useful to be able to change it and fix things.
    • You may want to do a "reveal", where people mint NFTs before the individual traits have been revealed. After all NFTs have been minted, you can change the uris to point to the real metadata.
    • You may actually want to update the NFTs in the future, if that's a feature of your collection.

    For example, Bored Ape Yacht Club and all Studio 721 contracts include the smart contract function setBaseURI, which allows changing token uris.

    Bored Ape Yacht Club also includes a separate hash of all of their metadata files, which they call "provenance", so that it's obvious if something changes. There's a newer approach that can do this in the token uris themselves, by uploading a Car file to IPFS, which is what Studio 721 Artkit does.

    The contract owner can also theoretically call the renounceOwnership function to revoke their own ability to change the uris, though this rarely happens in practice. Most NFT creators have good intentions, so the ability to change the uris is only used for legitimate reasons, and is generally supported by collectors.