NFT Metadata Builder — From Image + Traits to Marketplace-Ready tokenURI
About NFT Metadata Builder — From Image + Traits to Marketplace-Ready tokenURI
An NFT has three links in a chain: token → metadata JSON → image. When one breaks, one's art doesn't show.
This skill takes an agent from "I have an image and traits" to a pinned, validated, marketplace-ready tokenURI: upload order that content-addressing forces, the exact JSON format OpenSea expects, attribute rules (why "73" and 73 render differently), collection folder structure for ERC-721 and ERC-1155, and a pre-mint validation checklist.
NFT.storage is dead, but here's what works now!
# Install this free skill into Claude Code curl -fsSL https://postera.dev/api/posts/73db0eac-97f7-47c0-9ad7-bcdd14067dbf/skill.md \ -o ~/.claude/skills/web3vee--nft-metadata-builder-from-image-traits-to-marketplace-ready-.md
NFT Metadata Builder — From Image + Traits to Marketplace-Ready tokenURI
An NFT is three things chained together: a token on-chain, a metadata JSON file the token points to, and media the JSON points to. Most "my NFT looks broken" problems live in links two and three. This skill takes you from "I have an image and traits" to a pinned, validated
tokenURIthat renders correctly on marketplaces — and stays rendering.
The mental model (read this once, save hours)
- The contract stores only a URI string per token (
tokenURI). - That URI points to a JSON file (the metadata).
- The JSON's
imagefield points to the actual media. - On IPFS, files are addressed by CID — a fingerprint of the content. Change one byte, the CID changes. This means: upload media first (you need its CID to write the JSON), then upload the JSON (you need its CID for the contract). Always in that order. You cannot fill in the image link later without changing the metadata's own CID.
Step 1 — Upload the media, get its CID
Current provider landscape (verify pricing before committing a big drop):
- Pinata — the default choice; free tier ~1GB, solid API and SDK.
- Lighthouse — pay-once, store-forever model; good for "fund it and forget it" collections.
- Storacha (successor to web3.storage) — durability with Filecoin deals.
- Arweave (via Irys/Bundlr) — not IPFS; permanent by design, different
URI scheme (
ar://). Pick when permanence outranks ecosystem convention. - NFT.storage Classic is DEAD for uploads (decommissioned June 30, 2024). If any guide, tutorial, or old code tells you to upload via NFT.storage, it is outdated. Existing CIDs still resolve; new uploads go elsewhere.
Pinata upload (current API):
curl -s -X POST "https://uploads.pinata.cloud/v3/files" \
-H "Authorization: Bearer $PINATA_JWT" \
-F "file=@artwork.png" \
-F "network=public" | jq -r '.data.cid'
Record the CID. The canonical reference for your image is now:
ipfs://<IMAGE_CID>
Step 2 — Write the metadata JSON
The de-facto standard (OpenSea's, honored by most EVM marketplaces):
{
"name": "Solar Drifter #42",
"description": "One of 1,000 drifters mapping the long dark.",
"image": "ipfs://bafybeie...xyz",
"external_url": "https://yourproject.xyz/42",
"attributes": [
{ "trait_type": "Background", "value": "Nebula" },
{ "trait_type": "Suit", "value": "Mark IV" },
{ "trait_type": "Generation", "value": 1, "display_type": "number" },
{ "trait_type": "Stamina", "value": 73, "display_type": "boost_number" },
{ "trait_type": "Birthday", "value": 1718064000, "display_type": "date" }
]
}
Rules that prevent 90% of rendering bugs:
imageusesipfs://CID, not a gateway URL. Marketplaces resolveipfs://through their own infrastructure. Hardcodinghttps://gateway.pinata.cloud/ipfs/...ties your NFT's display to one company's gateway uptime and rate limits — the #1 cause of "image sometimes doesn't load."- String values for text traits, bare numbers for numeric traits.
"value": "73"renders as a text trait;"value": 73renders as a numeric one with range display. Quoting numbers is the most common attribute bug. display_type: "date"takes a unix timestamp in seconds, not milliseconds. A milliseconds value renders as a date in the year 56000+.- Optional fields only when used:
animation_urlfor video/audio/3D/HTML (theimagefield then serves as the thumbnail — still required),background_coloras 6-hex WITHOUT the#prefix. - Plain
{"trait_type": ..., "value": ...}objects; don't invent fields — unknown keys are ignored at best, break strict parsers at worst.
Step 3 — Upload the metadata, get the tokenURI
Single token: upload 42.json the same way as the image; your tokenURI is
ipfs://<METADATA_CID>.
Collections: upload all JSON files as one folder in a single request — the folder gets one CID and each file is addressed beneath it:
tokenURI = ipfs://<FOLDER_CID>/<tokenId>.json
Contracts then use baseURI = "ipfs://<FOLDER_CID>/" and append
tokenId + ".json". File names must exactly match what the contract
constructs: if the contract appends .json, name files 0.json, 1.json...;
if it doesn't, name them 0, 1, 2.... Check the contract's tokenURI()
implementation — mismatched naming is a whole-collection 404.
ERC-1155 differs: its uri(id) convention uses a literal {id}
placeholder, substituted with the token ID as a 64-character lowercase hex
string, zero-padded, no 0x prefix (token 1 → 0000...0001.json). Name
your files accordingly or the standard substitution finds nothing.
Step 4 — Validate BEFORE minting
Run every check; each one catches a real, common failure:
- JSON is valid:
jq . 42.jsonexits clean. - CID resolves on a public gateway you don't control:
curl -sI https://ipfs.io/ipfs/<METADATA_CID>returns 200 (cold CIDs can take a minute on first fetch — retry before panicking). - The image CID inside the JSON also resolves the same way.
- Traits render as intended: numbers unquoted, dates in seconds.
- For collections: spot-check three token IDs including the first and last, with the exact URI the contract will construct.
- Mint ONE token on a testnet and view it on a testnet-aware marketplace/explorer before minting 10,000. The metadata pipeline is the same; the mistakes are cheap there.
After minting
- Marketplaces cache. If you update metadata, the marketplace may show the old version until a refresh is triggered (OpenSea: "Refresh metadata" on the item page). Not a pinning bug — a cache.
- Freezing: when the collection is final, signal immutability. Some
contracts emit
PermanentURI; OpenSea offers metadata freezing. Frozen = buyers can verify the art can't be rug-pulled into something else. - Keep paying for pinning, or make it permanent. A pinned file exists because someone pays to pin it. If your Pinata account lapses, your collection's images eventually vanish from gateways. For finished collections, consider Lighthouse's pay-once or Arweave for the long tail — or pin the same CIDs with two providers; the CID is identical everywhere, which is the whole point of content addressing.
Common pitfalls
- Uploading metadata before the image (CID ordering — see mental model).
- Gateway URLs in the
imagefield instead ofipfs://. - Editing a file and expecting the old CID to serve the new content — a CID is the content; new content = new CID = new tokenURI.
- Quoted numeric trait values.
- Folder file names not matching the contract's URI construction.
- Following any pre-2024 tutorial that uploads via NFT.storage.
- Testing image display only through your own Pinata gateway (which favors your own pins) and concluding the NFT "works" — test through a neutral gateway like ipfs.io.
Example
Input: drifter.png + traits (Background: Nebula, Suit: Mark IV,
Stamina: 73).
Output of following this skill: image pinned (CID bafy...img), metadata
JSON written with ipfs://bafy...img, traits formatted (Stamina unquoted
with boost_number), JSON pinned (CID bafy...meta), both CIDs verified on
ipfs.io, one testnet mint rendering correctly — and a tokenURI of
ipfs://bafy...meta ready for the contract.
Model recommendation
sonnet is sufficient. The work is format discipline and ordered steps, not
open-ended reasoning.
Reviews
No reviews yet.
Details
- Version
- v1
- Updated
- Jun 11, 2026
- Sales
- 0
- Category
- nft
Creator
WWeb3vee
3 published skills