Setting Up NFT Metadata Storage on IPFS
IPFS is content-addressed storage: file address (CID) is hash of its content. Changing file at same address is impossible — this fundamental property makes IPFS the right choice for NFT metadata. If contract stores ipfs://QmXxx... in tokenURI — art and metadata are bound to specific content forever.
Metadata Structure and Pinning
What and How We Upload
Standard set for NFT collection:
-
images/— PNG/WebP token files, 1000x1000px minimum -
metadata/— JSON files, one per token - Optional: animations (MP4/WebM), 3D models (GLB)
Each metadata JSON contains image field with IPFS URI of image. Upload in two stages: first upload folder with images, get directory CID, then generate JSON with this CID in image fields, upload JSON folder. This way metadata IPFS CID is fixed and contains references to fixed images — complete immutability chain.
Pinning: Why It's Critical
IPFS by default deletes files via garbage collection if there's no active pin. Without pinning, files exist only while someone actively requests them. For NFT this is unacceptable.
Pinata — most popular service. API for programmatic pinning, dedicated gateway URLs, IPFS + Arweave support. Has free tier for testing.
NFT.Storage (now nft.storage/storacha) — free NFT data storage via Filecoin. Reliable long-term storage, but less flexible in API.
Web3.Storage — Filecoin-backed storage with HTTP gateway. Alternative for those wanting redundancy via Filecoin deals.
For serious collections we recommend double-pin: Pinata + Arweave. Arweave is permanent storage with one-time payment, data stored forever via endowment mechanism. Cost — ~$5-15 per GB. For 10k token collection with 5 MB images — ~$300 for eternal storage.
Practical Setup
Upload script via Pinata API (Node.js):
import { PinataSDK } from "pinata";
const pinata = new PinataSDK({ pinataJwt: process.env.PINATA_JWT });
// Upload images folder
const imagesUpload = await pinata.upload.folder("./images");
const imagesCID = imagesUpload.IpfsHash;
// Generate and upload metadata
const metadata = tokens.map((id) => ({
name: `Collection #${id}`,
image: `ipfs://${imagesCID}/${id}.png`,
attributes: traits[id]
}));
const metaUpload = await pinata.upload.folder(metadata, { name: "metadata" });
CID from metaUpload.IpfsHash — this goes into baseURI of contract.
Availability Check
After upload check availability via public IPFS gateways (cloudflare-ipfs.com, ipfs.io). Important: don't hardcode specific gateway in contract — only ipfs://CID/. Marketplaces (OpenSea, Blur) resolve IPFS URI through their gateways.
Timeline Reference
Setting up pinning and uploading metadata for ready collection — 1 work day. Includes: images upload, JSON generation, pinning to Pinata + backup Arweave, gateway availability check, CID handoff to contract.







