Near Learn

NEP-177: NFT metadata

NEP-177 NFT metadata standard on NEAR: contract-level nft_metadata with spec "nft-1.0.0", per-token title, media, media_hash and reference fields.

Intermediate6 min read3-question check

NEP-177 defines the display data for NFTs at two levels. Contract metadata describes the collection and is returned by the nft_metadata() view. Token metadata describes each token and is returned inside the metadata field of nft_token, nft_tokens and friends.

Unlike ERC-721, where tokenURI points to a JSON file somewhere off-chain, NEP-177 keeps the core fields on-chain in the token record and uses links only for the heavy media and optional extra JSON.

Contract metadata#

FieldMeaning
specMust be "nft-1.0.0".
name, symbolCollection name and ticker.
iconOptional small image, ideally a data URL.
base_uriOptional gateway (e.g. an IPFS gateway) that relative media / reference values are resolved against.
reference, reference_hashOptional link to extra collection JSON plus its base64 sha256 (the hash is required when reference is set).

Token metadata#

FieldMeaning
title, descriptionHuman-readable name and description of this token.
media, media_hashLink to the image/video (preferably decentralized storage) and the base64 sha256 of that file, so clients can verify it.
copiesNumber of copies of this set of metadata in existence when the token was minted (editions).
issued_at, expires_at, starts_at, updated_atTimestamps as Unix epoch in milliseconds.
extraFree-form string for anything else — often stringified JSON for traits or game data.
reference, reference_hashLink to an off-chain JSON with more attributes, plus its base64 sha256.
A token as returned by nft_token (illustrative values)
JSON
{
  "token_id": "42",
  "owner_id": "alice.near",
  "metadata": {
    "title": "Sunrise #42",
    "description": "One of 100 sunrises.",
    "media": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
    "media_hash": "Vdr1nZ8vWqU2hD2nX6m7rT5fKpNs0Y1cJbQwEaLxZ3o=",
    "copies": 100,
    "issued_at": 1735689600000,
    "expires_at": null,
    "starts_at": null,
    "updated_at": null,
    "extra": "{\"rarity\":\"rare\"}",
    "reference": null,
    "reference_hash": null
  },
  "approved_account_ids": {}
}
Where token metadata lives
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract Sunrise is ERC721 {
    constructor() ERC721("Sunrise", "SUN") {}

    // metadata is an off-chain JSON pointed at by a URI
    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://bafy.../";
    }
}
Rust
use near_contract_standards::non_fungible_token::metadata::TokenMetadata;
use near_contract_standards::non_fungible_token::{Token, TokenId};
use near_sdk::{env, near, require, AccountId};

#[near]
impl Contract {
    // metadata is stored on-chain with the token (tokens: NonFungibleToken
    // was created with a TokenMetadata storage prefix)
    #[payable]
    pub fn nft_mint(
        &mut self,
        token_id: TokenId,
        receiver_id: AccountId,
        metadata: TokenMetadata,
    ) -> Token {
        require!(env::predecessor_account_id() == self.tokens.owner_id, "Unauthorized");
        // internal_mint charges the attached deposit for the new storage
        self.tokens.internal_mint(token_id, receiver_id, Some(metadata))
    }
}
Coming from EVM

tokenURI(id) ↔ the metadata object on nft_token(id). If media is relative, clients prefix it with the contract’s base_uri.

internal_mint stores the metadata, refunds unused deposit and emits the NEP-297 nft_mint event for you.

Check yourself

3 questions · progress saved in this browser

  1. 1.Where does a NEAR wallet find an NFT’s title and image link?
  2. 2.What is media_hash for?
  3. 3.A token’s media is "bafy.../42.png" (no scheme). How should a client resolve it?