
ERC-1155 metadata on IPFS follows different rules from ERC-721, because an ERC-1155 contract usually stores one URI with a placeholder instead of one URL per token. Every wallet and marketplace fills in that placeholder itself. It works smoothly on a normal web server and fails on IPFS, where a file name is part of what you pin. This guide covers the exact {id} rule from the standard, a folder layout that satisfies it, and the per-token alternative for when a single folder does not fit.
TL;DR:
uri(uint256 id), not tokenURI(), and the standard lets one URI containing {id} cover every token{id} with the token ID in lowercase hexadecimal, no 0x prefix, zero-padded to 64 characters, so token 1 becomes 000...0001ipfs://<FOLDER_CID>/{id}.jsonERC1155URIStorage extension stores a separate URI per token instead, which removes the naming rule but adds one on-chain value per tokenThe metadata rules live in the EIP-1155 specification. The optional metadata extension defines one function, uri(uint256 _id), which returns a URI pointing at a JSON file. ERC-1155 has no tokenURI(); that name belongs to ERC-721, and calling it on an ERC-1155 contract is a common mix-up.
The part that causes trouble is the substitution rule. If the string {id} appears in a URI, clients must replace it with the actual token ID in hexadecimal. The spec adds two format requirements: the hex string is lowercase [0-9a-f] with no 0x prefix, and it is leading-zero padded to 64 characters. The EIP gives an example: https://token-cdn-domain/{id}.json becomes https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json for token ID 314592, which is 0x4CCE0 in hex.
The same rule applies to any JSON value in the metadata file, so a field can contain {id} too. The reason for the design is that a large number of tokens can share one on-chain string.
OpenZeppelin's base ERC1155 contract follows the same model. Its API documentation says the default uri() returns the same URI for all token types and relies on the ID substitution mechanism, so clients must replace the {id} substring. OpenSea's metadata standards page shows the same pattern for ERC-1155 with an IPFS address, ipfs://bafy.../{id}.json, and the same 314592 example.
On a conventional server, a rewrite rule can map any padded ID to a file. IPFS has no rewrite layer. A path like ipfs://<FOLDER_CID>/<name> resolves only if a file with exactly that name exists in the pinned directory. So a folder that holds 1.json, 2.json, and 3.json looks reasonable and fails, because the client asks for the 64-character name.
These are the mistakes that show up most often:
1.json) because the token ID is easy to read that way4cce0.json)0x prefix{id} in the contract URI and forgetting the .json extension in the file names, or the reverseThe EIP requires clients to request the padded form. Some tutorials show shortcuts that appear to work on one marketplace, so follow the spec and test on each client you care about.
The layout is one metadata folder, one images folder, and file names generated from the token IDs:
metadata/
0000000000000000000000000000000000000000000000000000000000000001.json
0000000000000000000000000000000000000000000000000000000000000002.json
0000000000000000000000000000000000000000000000000000000000000003.json
images/
1.png
2.png
3.png
Pin the images folder first, because its CID goes into every metadata file. AIOZ Pin's Node.js SDK pins a folder with pinFolderToIPFS, and the documentation says it returns the CID of the directory, so you can link to each file by name as the path part of an IPFS URI:
import AIOZPinClient from '@aioznetwork/aioz-pin-sdk'
const client = new AIOZPinClient(process.env.AIOZ_PIN_API_KEY, process.env.AIOZ_PIN_SECRET_KEY)
const options = { metadata: { name: 'game-items-images' } }
const images = await client.pinFolderToIPFS({ sourcePath: './images', options })
console.log(images) // note the directory CID
Generate the metadata file names instead of typing them. This script builds the padded name with BigInt and writes one JSON file per token, using the images CID from the previous step:
import fs from 'fs'
const IMAGES_CID = '<IMAGES_FOLDER_CID>'
const hexId = (id) => BigInt(id).toString(16).padStart(64, '0')
fs.mkdirSync('metadata', { recursive: true })
for (let id = 1; id <= 3; id++) {
const metadata = {
name: `Item #${id}`,
description: 'Example item',
image: `ipfs://${IMAGES_CID}/${id}.png`,
}
fs.writeFileSync(`metadata/${hexId(id)}.json`, JSON.stringify(metadata, null, 2))
}
hexId(314592) returns 000000000000000000000000000000000000000000000000000000000004cce0, the same string as the EIP's example. The images keep short names, because the image field holds a plain path and is only substituted if you put {id} in it.
Then pin the metadata folder the same way, with sourcePath: './metadata', and take its directory CID. Using OpenZeppelin's ERC1155, that CID goes into the constructor:
contract GameItems is ERC1155 {
constructor() ERC1155("ipfs://<METADATA_FOLDER_CID>/{id}.json") {}
}
Replace <METADATA_FOLDER_CID> with the directory CID of the metadata pin. Every token ID then resolves to its own padded file, and the contract stores a single string.
If the folder approach does not fit, ERC-1155 also allows a different URI for each token. OpenZeppelin's ERC1155URIStorage extension provides storage-based token URI management, with _setURI(tokenId, tokenURI) for one token and _setBaseURI(baseURI) for a shared prefix. Each token then points at its own CID, and the {id} rule and file naming no longer apply.
The trade-off is that you store a value on-chain for every token rather than one for the collection. This pattern also matches how AIOZ Pin's NFT API works: creating an NFT uploads an asset and a metadata JSON together and returns an asset_cid and a metadata_cid for that one NFT, which suits a per-token URI. The AIOZ Pin tutorial on ERC-1155 metadata uses the same flow and builds an ipfs:// URI from the metadata CID.
A folder has one CID, and a CID is derived from the content. Fix a typo in a single file and the folder gets a new CID, so the URI in your contract no longer matches the corrected folder. The old folder still resolves for as long as someone keeps it pinned, which is the property that makes IPFS metadata trustworthy and the reason updates need a plan.
A base ERC1155 keeps its URI in an internal _setURI(newuri) function, so a contract that may need to change the folder must expose a restricted function that calls it. Decide this before launch. If the collection should be permanently fixed, pin the final folder and leave it. Either way, the folder has to stay pinned, because metadata that nobody hosts returns nothing, whatever the contract says.
Does ERC-1155 use tokenURI or uri? ERC-1155 uses uri(uint256 id). The tokenURI(uint256) function belongs to ERC-721, and OpenSea reads whichever of the two matches the token standard.
What does {id} mean in an ERC-1155 URI? It is a placeholder that clients replace with the token ID in lowercase hexadecimal, without a 0x prefix, zero-padded to 64 characters.
Why does my ERC-1155 metadata return 404 on IPFS? The most likely cause is that the files in the pinned folder are not named with the 64-character hex ID, so the path the client builds does not exist in the folder.
Can I use one metadata file for every token? Yes. If the URI contains no {id}, every token resolves to the same file, which suits collections where all tokens share the same metadata.
Do I need a folder CID or can I pin files one by one? Pinning files one by one gives each its own CID and no common folder, so there is nothing to put before {id}. Use a folder for the {id} pattern, or use per-token URIs for individually pinned files.
What happens if I change one metadata file? The folder gets a new CID. You then need to update the contract's URI, and the old folder stays reachable only while it remains pinned.

ERC-1155 metadata on IPFS breaks when filenames ignore the {id} rule. Here is the exact 64-character format, a working folder layout, and the per-token option.

AIOZ Pin rate limits in one place: 180 requests per minute for the API, 15 per CID on the public gateway, and no predefined limit on premium gateways.

IPFS pin stuck on Pinning? Here is what Pinning, Pinned, and Failed mean on AIOZ Pin, why big directories can sit there, and how to read a pin's real state.

Two identical files, three IPFS tools, three different CIDs. Here is why that happened for years, and how IPIP-0499 made content addressing deterministic.

AIOZ Pin MCP server setup has two paths: llms.txt lets a super intelligence assistant read the API, the MCP server lets it call 11 tools. Here is each one.

AIOZ Pin bills three flat rates against a token wallet, with no tiers to pick between. Here is what storage, bandwidth and pinned files cost, with examples.