Abstract
This proposal specifies the JSON document that tokenURI (ERC-721, ERC-6909) and uri (ERC-1155) resolve to. It covers the display fields, the attributes array of traits, which URI schemes clients resolve, how clients treat malformed values, and how other proposals add fields. It writes down the format most deployed tokens already use.
Motivation
ERC-721 defines name, description, and image, and ERC-1155 adds decimals, properties, and localization. Everything else wallets and marketplaces show, including video, HTML, and traits, follows a format OpenSea documented in 2018 that was never specified. Clients fill the gaps differently: whether a trait value can be a boolean or a quoted number, whether dates are in seconds or milliseconds, which URI schemes to resolve, and how to isolate HTML.
Proposals that add metadata fields, such as ERC-5625, have no base document to require and no rules for naming fields or for what older clients show. This proposal gives them both.
Specification
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
A metadata URI is the string returned by tokenURI(uint256) in ERC-721 or ERC-6909, or by uri(uint256) in ERC-1155. A metadata document is the JSON object it resolves to. A producer publishes metadata documents, and a client, such as a wallet, marketplace, or indexer, reads them.
Resolving URIs
These rules apply to the metadata URI and to every URI-valued field.
Clients MUST resolve https, ipfs (InterPlanetary File System), ar (Arweave), and data URIs. Clients MAY resolve other schemes, such as ipns or the web3 scheme of ERC-4804, and MUST NOT resolve schemes that run code or read local resources, such as javascript or file.
Producers MUST use absolute URIs, SHOULD use ipfs, ar, or data for content that is not meant to change, and SHOULD NOT use http. An ipfs URI MUST have the form ipfs://<cid>, where <cid> is a content identifier, with an optional path. Producers SHOULD NOT write an IPFS gateway URL in place of an ipfs URI. Clients MAY fetch ipfs and ar content through any gateway.
A data URI (RFC 2397) that carries a metadata document SHOULD use the prefix data:application/json;base64,. Clients MUST accept base64 payloads. Clients SHOULD also accept unencoded payloads, such as data:application/json;utf8,{"name":"Example"}, and SHOULD percent-decode one that does not parse as written.
For ERC-1155 tokens, clients MUST apply ERC-1155's {id} substitution to the metadata URI and to string values in the document.
Fetching
A metadata document MUST be a JSON object (RFC 8259) encoded as UTF-8. Clients MUST treat any other top-level value as absent, and SHOULD parse the body as JSON whatever its Content-Type. Servers SHOULD send an accurate Content-Type and Access-Control-Allow-Origin: *.
A client that fetches from its own servers, such as an indexer, MUST NOT connect to loopback, private, or link-local addresses. It MUST apply this check to the address each hostname resolves to and to every redirect.
Fields
| Field | Type | Meaning |
|---|---|---|
name | string | Name of the token. |
description | string | Description of the token. |
image | URI | Still image, used in lists and previews. |
animation_url | URI | Video, audio, 3D model, or HTML page, shown in place of image. |
external_url | URI | Page about the token on the producer's site. |
background_color | string | Color behind the image. |
attributes | array | Traits of the token. |
Every field is OPTIONAL. Producers SHOULD include name and image, and SHOULD omit a field rather than set it to null or an empty string.
Clients MUST treat null and empty strings as absent and MUST ignore fields they do not recognize. Clients SHOULD ignore a field of the wrong type and read the rest of the document, and MAY truncate or drop values that exceed their own limits.
name and description
name is plain text. Clients MUST NOT interpret Markdown or HTML in it.
description MAY contain CommonMark Markdown. Clients MUST NOT render raw HTML in it and SHOULD NOT load remote images it references.
image
Producers SHOULD use a format browsers display natively, such as PNG, JPEG, GIF, WebP, AVIF, or SVG (Scalable Vector Graphics). Clients MUST display SVG without running its scripts, for example in an image element or by rasterizing it.
animation_url
Clients that can render animation_url SHOULD show it in the token's detail view in place of image, MUST fall back to image when they cannot, and SHOULD show image as cover art for audio. Producers SHOULD use one of these media types:
| Kind | Media types |
|---|---|
| Video | video/mp4, video/x-m4v, video/webm, video/ogg |
| Audio | audio/mpeg, audio/wav, audio/ogg |
| 3D model | model/gltf-binary, model/gltf+json (glTF 2.0) |
| Interactive | text/html |
Clients SHOULD take the media type from the Content-Type header or the data URI, and MAY fall back to the file extension.
A client that renders an HTML page:
- MUST isolate it from the client's origin, for example in an
iframesandboxed withallow-scriptsbut notallow-same-origin. - MUST NOT expose a wallet provider or signing interface to it.
- SHOULD block popups and top-level navigation from it.
- SHOULD resolve relative references against the page's own location.
external_url and background_color
Producers SHOULD use https for external_url.
background_color is six hexadecimal digits without a leading #, such as "E0F2FE", in either case. Clients SHOULD ignore other formats, and MAY remove a leading # first.
attributes
An array of trait objects:
| Field | Type | Meaning |
|---|---|---|
trait_type | string | Name of the trait. SHOULD be present. |
value | string or number | Value of the trait. REQUIRED. |
display_type | string | How clients present the value. |
max_value | number | Upper bound of a numeric value. |
value MUST be a string or a number, so "5" is a string trait and 5 is a numeric trait. Clients SHOULD ignore an attribute whose value is absent, null, an empty string, an object, or an array, and MAY read a boolean as the string "true" or "false".
A trait_type MAY repeat to give a trait several values, and clients SHOULD keep each value. Clients SHOULD compare trait types and string values exactly after trimming leading and trailing whitespace. Producers SHOULD list attributes in display order.
display_type | value | Meaning |
|---|---|---|
| absent | string | Categorical trait. |
| absent | number | Numeric trait. Clients MAY show it relative to max_value. |
string or text | string | The same as absent. Producers SHOULD write string. |
number | number | Numeric stat. |
boost_number | number | Signed bonus, such as +40. |
boost_percentage | number | Signed percentage. 10 means +10%. |
date | number | Unix time in seconds, which SHOULD be an integer. |
For number, boost_number, boost_percentage, and date, value MUST be a number, and clients MAY accept a string of a decimal number. Clients MUST treat a display_type they do not recognize as absent. max_value applies only to numeric traits and MUST be greater than or equal to value.
Other fields
decimals, properties, and localization have their ERC-1155 meaning for tokens of any standard. Producers SHOULD put traits in attributes, not properties.
Clients MAY read two legacy fields that producers SHOULD NOT use: image_data, raw SVG markup used when image is absent, and youtube_url, used when animation_url is absent.
Deployed forms
Clients MAY accept these forms, which producers MUST NOT use:
attributesas an object, such as{"Background": "Blue"}.- Traits in an array named
traitsorpropertiesinstead ofattributes. image_urlinstead ofimage.ipfs://ipfs/<cid>, read asipfs://<cid>.datevalues in milliseconds. Clients MAY read a value of1000000000000or more as milliseconds.
Extensions
An extension is a proposal that adds fields to the metadata document.
- An extension MUST list this proposal in
requires. - An extension MUST name every top-level field it defines, and SHOULD define a single top-level field whose value is an object.
- Field names SHOULD use only lowercase ASCII letters, digits, and underscores, and SHOULD begin with a letter.
- An extension MUST NOT change the meaning or type of a field defined by this proposal or another extension. It MAY define new
display_typevalues and new attribute fields. - A document that uses an extension MUST still conform to this proposal. An extension SHOULD state which base fields producers fill as a fallback.
- Clients that do not implement an extension MUST ignore its fields. Clients that do SHOULD prefer its data over the fallback.
Producers MAY add fields that no proposal defines, and SHOULD name them so they are unlikely to collide with an extension, for example by including the project's name.
Metadata updates
When a document changes, ERC-721 contracts SHOULD emit an ERC-4906 event and ERC-1155 contracts SHOULD emit URI. Clients that cache documents SHOULD refetch on these events, and MAY refetch on their own schedule.
JSON Schema
Where this schema differs from the text above, the text applies.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Token Metadata",
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"image": { "type": "string", "format": "uri" },
"animation_url": { "type": "string", "format": "uri" },
"external_url": { "type": "string", "format": "uri" },
"background_color": { "type": "string", "pattern": "^[0-9A-Fa-f]{6}$" },
"attributes": { "type": "array", "items": { "$ref": "#/$defs/attribute" } },
"decimals": { "type": "integer", "minimum": 0 },
"properties": { "type": "object" },
"localization": {
"type": "object",
"required": ["uri", "default", "locales"],
"properties": {
"uri": { "type": "string" },
"default": { "type": "string" },
"locales": { "type": "array", "items": { "type": "string" } }
}
},
"image_data": { "type": "string", "deprecated": true },
"youtube_url": { "type": "string", "format": "uri", "deprecated": true }
},
"$defs": {
"attribute": {
"type": "object",
"required": ["value"],
"properties": {
"trait_type": { "type": "string" },
"value": { "type": ["string", "number"] },
"display_type": { "type": "string" },
"max_value": { "type": "number" }
},
"if": {
"required": ["display_type"],
"properties": {
"display_type": { "enum": ["number", "boost_number", "boost_percentage", "date"] }
}
},
"then": { "properties": { "value": { "type": "number" } } }
}
}
}Example
{
"name": "Dave Starbelly",
"description": "A friendly sea creature that enjoys long swims in the ocean.",
"image": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/3.png",
"animation_url": "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/3.glb",
"external_url": "https://example.com/creatures/3",
"background_color": "E0F2FE",
"attributes": [
{ "trait_type": "Base", "value": "Starfish" },
{ "trait_type": "Level", "value": 5, "max_value": 10 },
{ "display_type": "boost_percentage", "trait_type": "Stamina Increase", "value": 10 },
{ "display_type": "date", "trait_type": "Birthday", "value": 1546360800 }
]
}Rationale
Figures below come from a survey of metadata from 708 Ethereum and Base contracts in October 2026.
- Field names stay as deployed.
animation_urlholds more than animations, andexternal_urldiffers fromexternal_linkin ERC-7572, but renaming either would break existing documents. - No field is required, because ERC-721 requires none and deployed documents often omit fields.
attributesis an array because traits repeat, have an order, and carry display hints, and most deployed documents already use one.- Unknown display types read as absent, so producers and extensions can add new ones without breaking clients.
textis accepted because more surveyed contracts use it thanstring. - Dates are in seconds to match
block.timestamp. The millisecond cutoff of1000000000000catches every millisecond date after September 2001 and no date in seconds before the year 33000. A lower cutoff would misread253402300800(the year 10000), which some contracts use to mean "never expires". - Gateway URLs tie content to one server: 113 of the 204 surveyed token URIs written as IPFS gateway URLs failed to load. An
ipfsURI can be fetched through any gateway or node. - Base64 is the recommended
dataencoding because browsers treat#as the start of a fragment and drop the rest of an unencoded payload, which breaks JSON that contains a#, such as a name likeCreature #42. Clients still accept unencoded payloads because many deployed contracts emit them. - Extensions are top-level objects because clients already detect optional data by checking whether a field is present. A list of implemented proposals, like the
interopobject in ERC-1046, or a version field would repeat that and could disagree with it. Names begin with a letter so they are valid identifiers in GraphQL and Protocol Buffers. - There is no contract interface or ERC-165 identifier, because the document tells a client everything an interface could.
- Attributes are display data that producers can change. Protocols that settle on a trait's value need an onchain source such as ERC-7496.
Backwards Compatibility
Documents that conform to the ERC-721 or ERC-1155 metadata schemas, or follow the 2018 format described in Motivation, conform to this proposal. The forms in Deployed forms do not conform, but clients may still accept them.
Test Cases
| Metadata URI | Document |
|---|---|
data:application/json;base64,eyJuYW1lIjoiRXhhbXBsZSJ9 | {"name":"Example"} |
data:application/json;utf8,{"name":"Example"} | {"name":"Example"} |
ipfs://ipfs/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/1.json | Not conformant. Read as ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/1.json by clients that accept it. |
For an ERC-1155 token with ID 314592, ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/{id}.json resolves to ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/000000000000000000000000000000000000000000000000000000000004cce0.json.
| Document | Result |
|---|---|
{"name": "Example", "image": 42} | image has the wrong type and is ignored. |
{"name": "Example", "description": null} | description is absent. |
{"name": "Example", "background_color": "#E0F2FE"} | Not conformant. Read as E0F2FE by clients that remove the #. |
{"name": "Example", "glow": {"color": "gold"}} | Conformant. glow is ignored. |
| Attribute | Result |
|---|---|
{"trait_type": " Eyes ", "value": "Big "} | String trait Eyes with value Big. |
{"value": "Happy"} | String trait with no trait type. |
{"trait_type": "Level", "value": "5"} | String trait. |
{"trait_type": "Level", "value": 5, "max_value": 10} | Numeric trait, 5 out of 10. |
{"display_type": "text", "trait_type": "Faction", "value": "Red"} | String trait. |
{"display_type": "sparkle", "trait_type": "Mood", "value": "Calm"} | String trait, since sparkle is unknown. |
{"display_type": "date", "trait_type": "Expires", "value": 253402300800} | 10000-01-01T00:00:00Z. |
{"display_type": "date", "trait_type": "Registered", "value": 1694173211000} | 2023-09-08T11:40:11Z for clients that accept milliseconds. |
{"trait_type": "Mood", "value": null} | Ignored. |
Two attributes with trait_type Accessory and values Scarf and Glasses form one trait with both values.
Security Considerations
- HTML in
animation_urland SVG images can run scripts. Rendered in the client's origin, they reach its cookies, storage, and connected wallet. The isolation rules above prevent this. - Links in
descriptionandexternal_urlcan lead to phishing pages. Clients should follow onlyhttpslinks and show the destination's domain. - Indexers fetch URIs that any contract deployer chooses. Without the address checks in Fetching, a URI or redirect can reach internal services such as a cloud instance metadata endpoint.
- Documents and media can be huge, slow, deeply nested, or compressed. Clients should limit size, depth, time, and redirects.
- A producer's server sees every client that fetches from it directly, and can serve moderators different content than users. Clients should fetch through their own cache.
- A document served over
httpscan change between a buyer's decision and settlement, so a seller can change a token's attributes to fill a trait offer and then change them back. Content-addressed URIs change only when the contract returns a new one. Protocols that settle on traits should read them onchain. - Anyone can copy another collection's name and image. Clients should identify tokens by chain, contract, and token ID, never by metadata.
Copyright
Copyright and related rights waived via CC0.
