NFT Metadata Development (on-chain/off-chain)
A wrong metadata architecture choice leads to data loss or unnecessarily high gas during minting. We are a team of blockchain engineers experienced in smart contract and NFT collection development. We help you choose the architecture: on-chain for maximum decentralization or off-chain on IPFS for complex visuals. We develop metadata for your collection—from concept to deployment.
tokenURI() returns a string—a URL or base64-encoded JSON. Behind this simplicity lies an architectural decision that will determine the collection's fate for years. Metadata on a centralized IPFS gateway is not decentralized; it's a link to a Pinata server that could disappear. An NFT with metadata on the contract will outlive any hosting. The average deployment cost of an on-chain collection of 10,000 tokens is about 0.1 ETH with optimal packing, whereas off-chain is less than 0.01 ETH.
On-chain or off-chain: which to choose?
Fully on-chain
Metadata is stored directly in the smart contract. tokenURI() generates JSON and SVG at runtime via string concatenation:
function tokenURI(uint256 tokenId) public view override returns (string memory) { string memory json = Base64.encode(bytes(string(abi.encodePacked( '{"name":"Token #', Strings.toString(tokenId), '","description":"On-chain NFT","image":"data:image/svg+xml;base64,', Base64.encode(bytes(_generateSVG(tokenId))), '"}' )))); return string(abi.encodePacked("data:application/json;base64,", json)); } Advantage: complete permanence, no reliance on external services. Disadvantage: deployment gas grows with SVG size. For simple generative collections (Loot, Nouns-style) this works. For photographs—no.
Storing attributes in storage: mapping tokenId → struct with trait values. Each attribute is uint8 or bytes32 to save slots. uint8 attributes pack 32 into one storage slot.
IPFS off-chain
Standard approach for most collections. Metadata is uploaded to IPFS, tokenURI() returns ipfs://CID/tokenId.json. Critical requirement: do not use an HTTP gateway in the URI.
Correct: ipfs://QmHash/1.json Incorrect: https://ipfs.io/ipfs/QmHash/1.json
The second is a link to a specific HTTP server—it may disappear. The first is a content address that works with any IPFS gateway.
For pinning—Pinata + Web3.Storage as backup. For the most important collections—Filecoin via NFT.Storage for long-term storage with cryptographic guarantee.
Comparison of on-chain and off-chain
| Criterion | On-chain | Off-chain (IPFS) |
|---|---|---|
| Permanence | 100% (as long as the blockchain lives) | Depends on pinning services |
| Deployment gas | High (up to 24KB limit) | Low (only URI) |
| Metadata update | Impossible (immutable) | Possible (change CID) |
| Suitable for | Generative collections (Loot, Nouns) | Media-heavy (photos, videos) |
On-chain metadata is 3 times more reliable than off-chain when using a single pinning service without backup. However, for collections with hundreds of megabytes of media, off-chain remains the only realistic option.
How to optimize gas for on-chain metadata?
Attribute packing is key. If you store each attribute as a separate uint256, 10 attributes take 10 storage slots. Packing uint8 at 32 per slot reduces deployment gas by 40%. For a collection of 10,000 tokens, this saves ~0.04 ETH. Generating SVG via string concatenation without libraries saves up to 30,000 gas per tokenURI call. Use Base64-encoding of JSON directly in the contract—it's cheaper than returning a URL.
How does the reveal mechanism work?
Pre-reveal: all tokens show placeholder metadata. Post-reveal: real metadata is revealed. A naive implementation—owner simply changes baseURI—is centralized and trust-based.
Commit-reveal scheme with VRF: before mint, the owner commits a seed hash; after mint is complete, publishes the seed and calls Chainlink VRF to get a random offset. Metadata is shuffles deterministically via (tokenId + offset) % totalSupply. No one can know in advance which traits any token will get.
function fulfillRandomWords(uint256, uint256[] memory randomWords) internal override { revealOffset = randomWords[0] % maxSupply; revealed = true; } function tokenURI(uint256 tokenId) public view override returns (string memory) { require(revealed, "Not revealed yet"); uint256 metadataId = (tokenId + revealOffset) % maxSupply; return string(abi.encodePacked(baseURI, metadataId.toString(), ".json")); } Comparison of reveal methods
| Method | Trust | Gas on reveal | Randomness guarantee |
|---|---|---|---|
| Simple baseURI change | Full owner | 0 | No |
| Commit-reveal + VRF | None | ~50,000 gas | Yes (Chainlink VRF) |
Why is the reveal mechanism important for collection fairness?
Without a reveal mechanism, minters can analyze metadata before purchase—choosing only rare tokens. This destroys the collection's economics and trust. Commit-reveal ensures no one knows traits before purchase, and VRF provides random distribution. Investing 50,000 gas on reveal (less than $0.5 at 50 gwei) protects the collection's market capitalization from manipulation.
JSON metadata structure
The OpenSea ERC-721 metadata standard is described in EIP-721:
{ "name": "Token #1", "description": "Description text", "image": "ipfs://CID/1.png", "external_url": "https://project.xyz/token/1", "attributes": [ {"trait_type": "Background", "value": "Blue"}, {"trait_type": "Rarity", "value": "Legendary", "display_type": "boost_percentage", "max_value": 100} ] } display_type controls display in OpenSea. Numeric attributes: "number" (plain number), "boost_percentage" (progress bar), "boost_number" (modifier), "date" (unix timestamp → date).
For ERC-1155 the structure is similar, but tokenURI takes uint256 id and can use the {id} placeholder in the URI.
Stages of implementing the reveal mechanism
- Develop a smart contract with VRF support.
- Deploy the contract and upload placeholder metadata.
- After minting is complete: owner commits the seed and then calls fulfillRandomWords via Chainlink VRF.
- Set the revealed flag = true, after which tokenURI generates metadata considering the offset.
"NFT metadata should be stored in a way that ensures availability and integrity." — EIP-721 rationale.
What is included in metadata development
- Architecture selection (on-chain / off-chain / hybrid) with rationale
- Writing the smart contract with optimized tokenURI
- Metadata generation (TypeScript scripts, rarity weights, layers)
- Upload to IPFS with backup pinning
- Implementation of the reveal mechanism with or without Chainlink VRF
- Contract deployment and configuration of public methods
- Documentation for metadata updates (if off-chain)
- 2-week post-launch support
We have implemented 30+ NFT projects with a total market volume of over 500 ETH. Average gas reduction on on-chain metadata is 40% due to attribute packing and SVG optimization. Get a consultation on metadata architecture for your collection—from 2 days for off-chain to 5 days for on-chain with a reveal mechanism.
We use Foundry for testing and Slither for vulnerability detection. We guarantee the contract passes audit without critical errors. Contact us—we will offer the optimal solution for your project.







