Imagine: you launched an NFT collection of 10,000 tokens, all sold out. A year later, a holder tries to open the metadata—but it won't load because the server hosting the images is down. The tokenURI leads to nothing. Your project's reputation is on the line. This pain is familiar to anyone who stores NFTs on centralized hosting. The solution: decentralized storage on IPFS with Filecoin replication via NFT.Storage. Let's break down the integration from A to Z.
In over 10 years, we have completed 50+ integrations with NFT.Storage, Pinata, and other services, pushing batch uploads to 10,000 files in one round. Below are practical details that saved us hundreds of hours.
Why use NFT.Storage for NFT storage?
NFT.Storage is a service from Protocol Labs that provides free storage of NFT data on IPFS and Filecoin. Technically: you upload a file via the API, get a CID (Content Identifier) — a SHA-256 hash, a unique identifier. Data is replicated onto Filecoin for long-term storage (typical deal is 18 months with cryptographic proof). Unlike centralized servers, content is addressed by content — if the file changes, the CID changes. This is critical for NFTs: a tokenURI like ipfs://Qm.../1.json works even if the project site disappears.
For new projects, we recommend using the modern w3up client (v2) — it handles large collections faster and has a more flexible API. If you already have a legacy integration with the old REST API (v1), it still works, but we recommend migrating for better reliability.
How storage works
IPFS — content addressing
IPFS uses content addressing: the CID is calculated from a hash of the content. ipfs://QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/image.png is a specific file with a specific hash. It’s impossible to change the content without changing the CID — this guarantees immutability for NFT metadata.
Filecoin for persistence
IPFS alone does not guarantee storage: if no node pins the file, it disappears. NFT.Storage automatically creates Filecoin deals for uploaded data — decentralized long-term storage with cryptographic proof of storage. This sets it apart from simple IPFS pinning services (Pinata, Infura).
Filecoin is a blockchain for verifying data storage. By using it, you get a guarantee that data will be stored for a set period (usually 18 months), even if NFT.Storage goes out of business.
Limits and restrictions
After policy changes, the service stopped accepting new users through its main site, switching to the commercial web3.storage with paid plans. For existing projects, data remains accessible. For new ones, alternatives include Pinata, 4EVERLAND, or self-hosted IPFS nodes.
For production NFT projects, we recommend a hybrid approach: primary storage plus replication on 2-3 pinning services. This minimizes the risk of data loss.
| Service | Free tier | Filecoin replication | API |
|---|---|---|---|
| NFT.Storage | 1 GB | Yes | REST / w3up |
| Pinata | 1 GB | No | REST, SDK |
| web3.storage | 5 GB | Yes | w3up |
| 4EVERLAND | 1 GB | No | REST, SDK |
| Upload method | Usage | Speed for 10K files | CID |
|---|---|---|---|
| client.store() | One by one | ~1 hour | Different per NFT |
| client.storeDirectory() | Folder of images | ~20 minutes | One per folder |
| Pinata batch | via SDK | ~15 minutes | One per folder |
How to integrate NFT.Storage into a project
Uploading a collection
import { NFTStorage, File } from 'nft.storage' const client = new NFTStorage({ token: process.env.NFT_STORAGE_KEY }) // Uploading a single NFT with image and metadata const metadata = await client.store({ name: 'Collection #1234', description: 'Description here', image: new File([imageBuffer], 'image.png', { type: 'image/png' }), attributes: [ { trait_type: 'Background', value: 'Blue' } ] }) console.log(metadata.url) // ipfs://Qm.../metadata.json console.log(metadata.data.image.href) // ipfs://Qm.../image.png Batch upload for a 10K collection
For large collections — use storeDirectory to upload an entire directory under one CID:
const files = images.map((buffer, i) => new File([buffer], `${i}.png`, { type: 'image/png' }) ) const imagesCid = await client.storeBlob(new Blob([/* directory */])) In practice, using Pinata pinFileToIPFS with batch upload is more reliable for thousands of files.
How to verify correct upload?
After uploading, it’s important to check CID availability through public gateways: ipfs.io/ipfs/{CID}, cloudflare-ipfs.com/ipfs/{CID}, gateway.pinata.cloud/ipfs/{CID}. If the file is accessible on 2 out of 3 gateways within 30 minutes, the upload is successful. Always verify metadata before setting baseURI in the contract.
Best practices
Separate images and metadata by CID — this simplifies auditing and provides flexibility for reveal strategies. Upload images first, get the directory CID. Then generate JSON metadata with image: ipfs://{imagesCid}/{tokenId}.png and upload them. Changing metadata without changing the CID is impossible — a guarantee for buyers.
Store the CID as a constant in the contract. After the final reveal, the baseURI should be immutable. If setBaseURI is accessible to the owner indefinitely, that’s a trust assumption. Consider renounceOwnership for the URI-changing function after reveal.
Use IPFS scheme in tokenURI. Wallets and marketplaces expect ipfs:// scheme, not https://gateway.ipfs.io/. Return ipfs://{CID}/{tokenId}.json — each client uses its own gateway.
What’s included in the work
- Analysis of your project and selection of the optimal storage provider (NFT.Storage / Pinata / web3.storage).
- Development of batch upload scripts for images and metadata.
- Setup of API keys and CID verification.
- Smart contract integration: baseURI setup, renounceOwnership.
- Documentation with step-by-step instructions for reuse.
- Technical support during the collection upload period (1-2 days).
Work process
- Analysis (a few hours) — choose service, estimate volumes.
- Upload script development (1 day) — batch upload, verification, baseURI generation.
- Contract integration (1 day) — set URI, test on testnet.
- Testing — check availability on gateways, simulate mint.
- Deployment — upload to mainnet, reveal.
Time estimates
Integration as part of an NFT collection development — 1-2 days. Standalone — a few hours for an experienced team.
Need reliable integration? Contact us—we’ll set up storage, verify CIDs, and advise on provider selection. Order integration today.







