Skip to content
Back to Blog

NFT Metadata JSON Standard: A Practical Guide

Get the name, image, and attributes fields wrong and your NFT shows up blank on OpenSea. Here is the exact metadata JSON standard, field by field, with examples.

SZ
Founder, Molixa
12 min read
Share
NFT Metadata JSON Standard: A Practical Guide
Table of contents9 sections

The NFT metadata JSON standard is the small file that tells a marketplace what your token actually is: its name, its image, and its traits. Get three fields right (name, image, attributes) and OpenSea renders your NFT perfectly. Get them subtly wrong and your token shows up as a gray blank with no traits, even though the contract minted fine. This guide walks the standard field by field, covers the ERC-721 versus ERC-1155 differences the official docs gloss over, and shows you how to generate clean metadata for a whole collection without hand-editing 10,000 files.

Most creators discover the standard the hard way, after deploying. The contract works, the token exists on-chain, and then the listing looks broken. The fix is almost always in the JSON, not the Solidity. So before you mint, it is worth understanding exactly what marketplaces read and where the common traps are.

What the NFT Metadata JSON Standard Actually Is#

When a wallet or marketplace wants to display your NFT, it does not look at the image directly. It calls a function on your contract, tokenURI(tokenId) for ERC-721, which returns a URL. That URL points to a JSON file. The JSON file is your metadata, and it is what the standard governs.

So the chain stores the link, and the link stores the description. There are really two standards in play, and they agree on the core but differ on the framing:

  • ERC-721 / ERC-1155 (the on-chain interface): defines the tokenURI (or uri) function and a minimal suggested schema with name, description, and image.
  • The OpenSea metadata standard (the de facto display schema): a superset that adds attributes, external_url, animation_url, background_color, and the display_type system. This is the one marketplaces actually render.

In practice you write to the OpenSea schema because that is what gets shown. It is backward compatible with the ERC interface, so following it satisfies both.

Key point: the blockchain almost never stores your image or traits. It stores a pointer. If that pointer breaks or the JSON behind it is malformed, the NFT looks broken even though the token is perfectly valid on-chain.

The Core Fields Every NFT Needs#

Here is a minimal, valid metadata file. This is the shape that renders correctly on OpenSea, Blur, Magic Eden (EVM), and most wallets.

{
  "name": "Cosmic Fox #042",
  "description": "A hand-drawn fox from the Cosmic Foxes collection.",
  "image": "ipfs://bafybeigdyr.../042.png",
  "external_url": "https://cosmicfoxes.xyz/042",
  "attributes": [
    { "trait_type": "Background", "value": "Nebula" },
    { "trait_type": "Fur", "value": "Orange" },
    { "trait_type": "Eyes", "value": "Laser" }
  ]
}

Four fields do the heavy lifting:

  • name: the display title. Convention is Collection Name #TokenID. This shows as the big heading on the item page.
  • description: supports basic Markdown on OpenSea (line breaks, bold, links). Keep it short and human.
  • image: the URL to the artwork. PNG, JPG, GIF, and SVG all work. This is the single most common point of failure (more on that below).
  • attributes: the array of traits that powers the rarity filters buyers use to browse and price your collection.

If you only ship name, description, and image, the NFT will still render. It just will not have any traits to filter on, which on a generative collection is a serious miss.

The image field and the ipfs:// trap#

The biggest source of "my NFT is blank" is the image field pointing to something the marketplace cannot fetch. Three rules keep you safe:

  1. Use a stable, gateway-resolvable URL. ipfs://CID/file.png is preferred because marketplaces resolve the ipfs:// scheme natively. An HTTPS link to your own server works too but ties the art's survival to your hosting.
  2. Do not use a localhost or temporary URL. Marketplaces cache metadata. If they fetch a broken link once, you often have to manually trigger a refresh.
  3. Match the file to the extension. A .png URL serving a JPEG can fail silently in some renderers.

For deciding between IPFS and a normal server (and what "rug risk" means for buyers), the free NFT metadata generator outputs ipfs://-ready files so you do not have to assemble the URIs by hand.

How attributes and display_type Really Work#

The attributes array is where the standard gets interesting, and where the official docs are thinnest. By default, every attribute is rendered as a text trait. But display_type changes how a value is shown, and getting it right is what separates a polished collection from an amateur one.

display_typeWhat it doesExample use
(none)Plain text trait, shown as a pillBackground: Nebula
numberNumeric trait with a collection-wide rankingGeneration: 2
boost_numberAdds a + and shows as a boost meterSpeed: +20
boost_percentageSame as boost, rendered as a percentageStamina: +10%
dateRenders a Unix timestamp as a calendar dateBirthday: Jan 2024

Here is the catch most guides skip: when you use a numeric display_type, the value must be an actual number, not a string. "value": 20 works; "value": "20" can render incorrectly or get dropped.

{
  "attributes": [
    { "trait_type": "Level", "display_type": "number", "value": 7 },
    { "trait_type": "Power", "display_type": "boost_number", "value": 40 },
    { "display_type": "date", "trait_type": "Minted", "value": 1704067200 }
  ]
}

Notice the last entry has a display_type and no trait_type label on the date in some setups. Both forms exist in the wild; including trait_type is the safer, clearer choice.

Ranking traits and the max_value field#

For numeric stats you can add max_value to force a consistent denominator across the collection, so a stat reads as 7 / 10 instead of just 7. This is purely a display nicety, but it makes RPG-style collections look intentional.

{ "trait_type": "Strength", "display_type": "number", "value": 7, "max_value": 10 }

ERC-721 vs ERC-1155 Metadata: The Difference Nobody Explains#

This is the gap that trips up creators moving from one-of-one art to editions or game items. The two standards handle the metadata URI differently, and the official OpenSea docs barely mention it.

AspectERC-721ERC-1155
Token modelOne unique token per IDMultiple copies per ID (editions)
URI functiontokenURI(uint256 id)uri(uint256 id)
ID substitutionUsually a full per-token URLStandard supports an {id} placeholder
Typical metadataOne JSON file per tokenOften one template resolved per ID
Decimals fieldNot usedOptional decimals for fungible-style tokens

The headline difference is the {id} placeholder. ERC-1155 lets your contract return a single URI template like ipfs://CID/{id}.json, and clients substitute the token ID. The official ERC-1155 spec requires the substituted ID to be a lowercase, zero-padded 64-character hexadecimal string with no 0x prefix. So token ID 1 becomes 0000000000000000000000000000000000000000000000000000000000000001.

In practice, OpenSea and most marketplaces are lenient and also accept a plain decimal ID, but if you want to be spec-correct, pad it. This single detail causes hours of "why won't my 1155 metadata load" confusion that no beginner tutorial warns you about.

ERC-1155 metadata can also carry a decimals field (for semi-fungible tokens) and a properties object alongside attributes. If you are building game items or tickets rather than art, read both fields.

Generating Metadata for a 10,000-Item Collection#

Hand-writing one JSON file is easy. Hand-writing 10,000 with consistent traits, correct numeric types, and matching image CIDs is where projects break. Doing it manually guarantees typos, and a typo in one file means one broken NFT a buyer will complain about.

The reliable approach is to treat metadata as generated output, not hand-edited files. Whether you script it or use a tool, the pipeline is the same.

Step 1: Lock your trait schema first#

Before generating anything, write down every trait_type and its allowed values. "Background" should always be spelled "Background," never "BG" in some files. Inconsistent trait names split your rarity filters into useless duplicates on the marketplace. One canonical list prevents that.

Step 2: Decide image hosting and get your CID#

Upload your full image folder to IPFS (via Pinata, NFT.Storage, or similar) so you get one base CID for the directory. Your image field then becomes ipfs://BASE_CID/042.png. Pinning the whole folder once keeps every URI consistent and avoids per-file uploads.

Step 3: Generate one JSON file per token#

For each token, assemble name, description, image (base CID plus filename), and the attributes array for that specific combination. Keep numeric traits as real numbers. The fastest no-code path is to paste your trait data into the NFT metadata generator, which builds spec-correct files with the right display_type and ipfs:// URIs, then exports the batch.

Step 4: Validate before you upload#

Run every file through a JSON validator so a single trailing comma does not break a token. Pasting a sample into a JSON formatter and validator catches malformed brackets, bad quotes, and the stray comma that breaks JSON.parse. Do this on a few random files from the batch, not just file #1.

Step 5: Upload metadata and set your baseURI#

Upload the metadata folder to IPFS to get a second CID, then set that as your contract's baseURI (so tokenURI returns ipfs://METADATA_CID/{id}.json or per-token URLs). Confirm one token resolves end to end on a testnet before you mint the full supply.

Warning: marketplaces aggressively cache metadata. Always verify your metadata renders correctly on a testnet (or with one mint) before committing the whole collection. Fixing a CID after a 10,000-mint reveal is painful and sometimes requires every holder to manually refresh.

On-Chain Metadata: When the JSON Lives in the Contract#

There is a fully on-chain variant where the tokenURI returns a base64-encoded data URI instead of an IPFS link, so the metadata (and sometimes an SVG image) lives entirely on Ethereum. This is how projects like fully on-chain generative art guarantee permanence with no external dependency.

The metadata schema is identical. The only change is delivery: instead of an ipfs:// link, tokenURI returns:

data:application/json;base64,eyJuYW1lIjoiQ29zbWlj...

The browser decodes that base64 string into the same JSON object described above. If you are debugging one of these, you can paste the encoded string into a base64 encoder and decoder to read the underlying JSON. On-chain storage costs far more gas, so it is reserved for collections that treat permanence as the whole point. For everything else, IPFS plus a clean JSON file is the standard.

Common Mistakes That Make Your NFT Render Blank#

A quick checklist of the failures that account for most "it minted but looks broken" support threads:

  • Trailing commas or unquoted keys. Valid in JavaScript objects, invalid in JSON. One comma breaks the whole file.
  • String values where numbers belong. "value": "7" with display_type: number can drop the trait. Use 7.
  • Mismatched trait_type spellings. "Eye Color" in one file and "Eyes" in another fragments your filters.
  • A broken or private image URL. Localhost, an expired link, or a typo'd CID renders a blank.
  • ERC-1155 ID not padded. If a marketplace is strict, an unpadded {id} fails to resolve.
  • Forgetting to refresh metadata. After fixing a file, you often have to trigger a metadata refresh on the marketplace because of caching.

If your contract is built but you are unsure the standard interface is wired correctly, a quick pass through a token contract checker confirms the tokenURI / uri function is exposed the way marketplaces expect before you spend gas minting.

The NFT Metadata JSON Standard in One Sentence#

The nft metadata json standard is a small JSON file with name, description, image, and an attributes array, served from tokenURI (ERC-721) or uri (ERC-1155), where numeric traits use a display_type with real numbers and the image lives on a stable ipfs:// URL. Follow that shape, validate before you upload, and verify on a testnet, and your collection renders the way you designed it. Generate the files with the NFT metadata generator and you skip the hand-editing entirely.

Frequently Asked Questions#

What is the NFT metadata JSON standard? It is the agreed-upon JSON schema that marketplaces read to display an NFT. At minimum it includes name, description, and image, plus an attributes array for traits. The file is served from your contract's tokenURI (ERC-721) or uri (ERC-1155) function, and OpenSea's superset of this schema is what most platforms actually render.

Where is NFT metadata stored, on-chain or off-chain? Usually off-chain. The blockchain typically stores only a pointer (an ipfs:// link or HTTPS URL) returned by tokenURI, and the JSON itself lives on IPFS or a server. A fully on-chain variant returns a base64-encoded data URI so the metadata lives in the contract, but that costs far more gas and is the exception, not the norm.

Why does my NFT show up blank on OpenSea? Almost always a metadata problem, not a contract problem. The usual causes are a broken or private image URL, malformed JSON (a trailing comma is the classic), a CID typo, or an ERC-1155 token ID that is not zero-padded. Fix the file, then trigger a metadata refresh, because marketplaces cache aggressively.

What is the difference between ERC-721 and ERC-1155 metadata? The schema is nearly identical, but the delivery differs. ERC-721 exposes tokenURI and usually points to one JSON file per token. ERC-1155 exposes uri and supports an {id} placeholder, where the spec requires the ID to be lowercase, zero-padded to 64 hex characters. ERC-1155 also adds optional fields like decimals for semi-fungible tokens.

How do I add traits with rarity to NFT metadata? Put them in the attributes array, each as an object with trait_type and value. Marketplaces compute rarity automatically by counting how often each value appears across the collection, so consistent spelling matters. Use display_type of number, boost_number, or boost_percentage for numeric stats, and keep those values as real numbers rather than strings.

Do I need IPFS for NFT metadata? No, but it is strongly recommended. IPFS gives content-addressed, tamper-evident hosting so buyers know the art cannot be silently swapped or deleted. A normal HTTPS server works and marketplaces will read it, but the art's survival then depends on your hosting staying online, which is the "rug risk" buyers worry about.

More from Molixa

Try Molixa Tools

50+ free AI tools for content creation, SEO, coding, and more. No signup, no watermark.

Explore all tools