NFT Metadata Standards

NFT Metadata Standards

A brief introduction to the standards and formats of NFT metadata.

Overview

Today, the primary standards for Non-Fungible Tokens are the Ethereum ERC-721 Non-Fungible Token Standard, and the more recent Ethereum ERC-1155 Multi Token Standard. Platforms such as OpenSea have adopted these NFT standards, along with modifications of their own, to support a wide range of NFT projects. For the purposes of this document, the most important features of these NFT standards are their treatment of token metadata, and the standards around that metadata.

ℹ️
The following is taken liberally from the NFT Schools & IPFS NFT docs pages. NFT School | IPFS NFT

Most NFTs will need some kind of structured metadata to describe the token's essential properties. Many encodings and data formats can be used, but the de-facto standard is to store metadata as a JSON object, encoded to a UTF-8 byte string.

Here's an example of some JSON metadata for an NFT taken from the OpenSea metadata standards page:

{
  "description": "Friendly OpenSea Creature that enjoys long swims in the ocean.",
  "external_url": "https://openseacreatures.io/3",
  "image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
  "name": "Dave Starbelly",
  "attributes": [ ... ],
}

There are many ways to structure metadata for an NFT, and a lot of the details depend on the specific use cases for a given NFT platform. The example above uses the schema defined in the ERC721 standard mentioned previously. Generally speaking, NFT creators use well known standards so their NFTs will be viewable using standard wallets and other tools like block explorers. Opensea’s metadata doc or Enjin’s metadata doc are good starting places to learn more about the expected content in your metadata.

NFT metadata example with fully expanded attributes
{
  "description": "Friendly OpenSea Creature that enjoys long swims in the ocean.",
  "external_url": "https://openseacreatures.io/3",
  "image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png",
  "name": "Dave Starbelly",
  "attributes": [
	    {
	      "trait_type": "Base", 
	      "value": "Starfish"
	    }, 
	    {
	      "trait_type": "Eyes", 
	      "value": "Big"
	    }, 
	    {
	      "trait_type": "Mouth", 
	      "value": "Surprised"
	    }, 
	    {
	      "trait_type": "Level", 
	      "value": 5
	    }, 
	    {
	      "trait_type": "Stamina", 
	      "value": 1.4
	    }, 
	    {
	      "trait_type": "Personality", 
	      "value": "Sad"
	    }, 
	    {
	      "display_type": "boost_number", 
	      "trait_type": "Aqua Power", 
	      "value": 40
	    }, 
	    {
	      "display_type": "boost_percentage", 
	      "trait_type": "Stamina Increase", 
	      "value": 10
	    }, 
	    {
	      "display_type": "number", 
	      "trait_type": "Generation", 
	      "value": 2
	    }
  ]
}

In the following sections, we outline how to leverage Tableland to create and store mutable NFT metadata!

JSON metadata

This is the easy part. We can map the ERC1155 metadata standard JSON schema into table columns. This is almost identical to the ERC-721 metadata standard with the inclusion of decimals and properties for things like NFT traits.

JSON metadata example
{
	"name": "Asset Name",
	"description": "Lorem ipsum...",
	"image": "https:\/\/s3.amazonaws.com\/your-bucket\/images\/{id}.png",
	"properties": {
		"simple_property": "example value",
		"rich_property": {
			"name": "Name",
			"value": "123",
			"display_value": "123 Example Value",
			"class": "emphasis",
			"css": {
				"color": "#ffffff",
				"font-weight": "bold",
				"text-decoration": "underline"
			}
		},
		"array_property": {
			"name": "Name",
			"value": [1,2,3,4],
			"class": "emphasis"
		}
	}
}

There is also room for localization handling!

JSON metadata schema
{
    "title": "Token Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this token represents",
        },
        "decimals": {
            "type": "integer",
            "description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation."
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this token represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        },
        "properties": {
            "type": "object",
            "description": "Arbitrary properties. Values may be strings, numbers, object or arrays.",
        },
        "localization": {
            "type": "object",
            "required": ["uri", "default", "locales"],
            "properties": {
                "uri": {
                    "type": "string",
                    "description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request."
                },
                "default": {
                    "type": "string",
                    "description": "The locale of the default data within the base JSON"
                },
                "locales": {
                    "type": "array",
                    "description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)."
                }
            }
        }
    }
}

In general, the description is the same for all the items in a given collection, though, this need not be the case. The name however, is specific to each item in the collection. To enable the ERC721 specification behavior on our tables, we need to create a unique row identifier that matches the token identifier for each token we're trying to create/mint.

Smart Contract Methods

When a platform like Looksrare or Opensea displays an NFT, they will read these metadata documents to decide what to display, what filters are available, and what infromation to expose. The way they get the metadata is through the contract based functions available in ERC-721 and ERC-1155 compliant smart contracts. ERC-721’s expose a tokenURI() method while ERC-1155’s expose a uri() method.

If your project is storing NFT metadata in tables, you’ll want to update your tokenURI() or uri() method to get whatever app or system is reading your NFT the right information. Let’s take a look at how to do that below.

In both methods, tokenURI() or uri(), a NFT tokenId will be interpolated in the token’s baseURI so that the tokenURI method returns the token’s metadata like so (more on this below):

https://testnet.tableland.network/query?unwrap=true&extract=true&s=select%20json_object('name'%2C'Rig%20%23'%7C%7Cid%2C'external_url'%2C'https%3A%2F%2Ftableland.xyz%2Frigs%2F'%7C%7Cid%2C'image'%2Cimage%2C'image_alpha'%2Cimage_alpha%2C'thumb'%2Cthumb%2C'thumb_alpha'%2Cthumb_alpha%2C'attributes'%2Cjson_group_array(json_object('display_type'%2Cdisplay_type%2C'trait_type'%2Ctrait_type%2C'value'%2Cvalue)))%20from%20rigs_5_28%20join%20rig_attributes_5_27%20on%20rigs_5_28.id%3Drig_attributes_5_27.rig_id%20where%20id%3D1%20group%20by%20id%3B

For reference, see here for where this link leads to.

You can read more about writing smart contracts that generate the above SQL and metadata response in

.

CLI and SDK methods

For projects that wish to populate data tables outside without writing Solidity, this can be done using the CLI or SDK. You can read more about both of these approaches in