NFT Contract Factory with Off-Chain Metadata

When it comes to layer-1 blockchains, storing metadata on-chain is very expensive and doesn’t make sense for most people. The preferred way is to leverage IPFS while keeping a reference to each token’s attributes on the blockchain. Let’s discuss the simplest way to create an NFT contract factory and store its metadata off-chain.


The contract we’ll be deploying is the building block to allow others to mint your NFT. We’ll be uploading our images to IPFS and then storing the link in our contract.

There are many ways to store data on IPFS, so we won’t cover that in this post. All we need to concern ourselves with is the CID.


Adding data to IPFS produces a content identifier (CID) that’s directly derived from the data itself and links to the data in the IPFS network.


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    event CreatedNFT(uint256 indexed tokenId, string tokenURI);

    constructor() ERC721("My NFT Collection", "MNC") {}

    function mint(string memory _tokenURI) public returns (uint256) {
        uint256 newItemId =  _tokenIds.current();
        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, _tokenURI);
        emit CreatedNFT(newItemId, _tokenURI);

        return newItemId;


The first thing to note is we are importing the ERC721URIStorage contract from Openzeppelin. This contract implements the ERC721 token standard, so all of its interfaces will be available to us (IERC721Metadata and IERC721Enumerable). From there, our token is created with the constructor by setting the name and symbol.

Next, we have our function responsible for the actual creation of the NFT. It’s important to note that our mint function is a public function, so, in this example, it can be called by any contract/person.

For our purposes, this is fine for two reasons: The first is that the “minter” is the one who pays the gas fee, and second, the factory will mint new tokens dynamically as opposed to all at once at the onset.

The safeMint function takes the address of our caller/minter and the tokenId as parameters. Then we call setTokenURI, giving it our tokenId and the link to its metadata on IPFS.

Deploy Contract

After compilation, migration and deployment (For more information on deployment, see Create and Deploy Dapp to Truffle) in your Truffle console, instantiate a new instance of your contract.

let nft = await MyNFT.deployed();


And voila!

If your output says status: true you’ve successfully minted your first NFT from the contract factory!

NFT contract factory

The contract factory we have created is one of the fundamental building blocks of generating a large collection. It allows users to mint as many of your NFTs as they’d like while storing the metadata off-chain via IPFS. This pattern is easily extensible by all web3 applications that might interact with your NFT.