Managing NFT Metadata with IPFS
In this guide, you will rely on an IPFS workflow for ERC721 collections on Somnia.
This guide will walk you through how - You will prepare and upload artwork to IPFS via Pinata. - Generate clean, wallet/marketplace-friendly metadata JSON - Upload the metadata to IPFS via Pinata Cloud - Deploy a Solidity contract that stores the metadata URIs onchain (for each token) - Mint your NFTs.
All metadata resides on IPFS, while only the URIs (pointers) are stored onchain.
Please note that this guide provides a Smart Contract option for fully onchain metadata.
Concepts You Should Know
IPFS (InterPlanetary File System) is a “Content Addressed Storage Network”. Files are addressed by their CID (content identifier). If any bit changes, the CID changes, and it is great for integrity and verifiability.
Pinata: Pinata pins your files to IPFS and keeps them available. You’ll get CIDs (content identifiers) that never change for the same content.
TokenURI: an ERC721 method that returns a URL (often an ipfs:// URI) pointing to a JSON file describing the NFT token (name, description, image, attributes, etc.).
Per token URI vs
baseURI:Per token URI (this guide): store the full JSON URI onchain for each token with ERC721URIStorage. Easiest and most flexible.
baseURIpattern: compute tokenURI = baseURI + tokenId (no per token storage). Cheaper for large drops, but requires sequential naming.
Prerequisites
MetaMask is configured for a Somnia RPC and has sufficient funds for gas.
Node.js ≥ 18 (only if you’ll run the helper scripts below).
Pinata account and a JWT (recommended): Get your JWT on Pinata
Pinata Dashboard → API Keys → New Key → choose Admin/Scoped as needed → copy JWT.
Prepare Images
If your art varies wildly in size or format, normalize once for a consistent user experience. Create an assets directory for your images. Run the node script below, targeting the assets directory to give uniformity to your images.
Install the dependencies:
Run the script:
Recommended Folder Structure
images/contain consistent dimensions and formats (e.g., 1024×1024 PNG).metadata/contain one JSON per token ID, matching the filename (e.g., 7.json for tokenId 7).
Upload Images to IPFS via Pinata
Install Pinata SDK:
Initialise Pinata using your JWT credentials:
Create a .env file in your project root:
Upload the images directory and get the root CID:
Run the following command to upload the images:
Copy the CID into .env as IMAGES_CID=....
Generate Metadata JSON
Each *.json should follow the de facto standards:
Install fast-glob library:
Run the script below to generate a file per image:
To create the metadata files, run the command:
Upload Metadata to IPFS
Upload the metadata folder to generate the METADATA_CID
Run the command:
Your tokenURI format is now: ipfs://<METADATA_CID>/<tokenId>.json Make sure the script uploads assets/metadata and records the printed Metadata CID; you’ll mint with: ipfs://<METADATA_CID>/<tokenId>.json
NFT Smart Contract
We’ll use ERC721URIStorage Smart Contract and mint with full URIs. Paste this into Remix as NFTTest.sol
Contract Walkthrough
Imports
ERC721is the core NFT logic (ownership, transfers, approvals).ERC721URIStorageadds per-token storage for URIs via_setTokenURI.Ownablesimple admin model; exposes onlyOwner for minting control.
ERC721URIStorage is the most straightforward way to store a token’s exact URI. For large drops, consider using a baseURI pattern to save storage gas; otherwise, this is perfect for flexible, explicit URIs.
State (_nextTokenId) is the sequential ID counter starting at 0. First mint → tokenId = 0, then 1, 2, … etc
Constructor
Initializes collection name/symbol and sets the owner to initialOwner (the only account allowed to mint).
_baseURI()
Returns https://ipfs.io. This is only used if you mint with relative paths (e.g., /ipfs/CID/7.json). If you mint with absolute ipfs://… URIs (recommended), this value is ignored.
safeMint(to, uri):
Owner-only mint. Creates a new token ID, mints safely (checking receiver contracts implement IERC721Receiver), and stores the exact uri string for that token via _setTokenURI.
Compile and Deploy using Remix. Follow this guide
Copy the deployed contract address.
How To Mint NFTs (Per Token URIs)
n Remix (Deployed Contracts):
Call
safeMint(to, uri)where:tois the recipient address (can be yours).uriisipfs://<METADATA_CID>/<id>.json(e.g.,ipfs://bafy.../0.json).
Then call tokenURI(0) to confirm the stored URI.
If you prefer scripts for multiple mints, you can write a script to Batch Mint using Hardhat, for example:
Validation
To validate everything end to end, first do quick gateway smoke tests by opening https://ipfs.io/ipfs/<IMAGES_CID>/0.png and https://ipfs.io/ipfs/<METADATA_CID>/0.json in a browser to confirm the image and JSON are reachable. Next, call tokenURI(0) on your contract and verify it returns ipfs://<METADATA_CID>/0.json. Finally, check in a wallet or marketplace UI that the NFT renders correctly using the ipfs:// image URI embedded in the JSON.
MetadataOptional Fully Onchain Metadata
Last updated