ERC721 mint with multiple URIs

Hi, I was thinking of a case where each uri is set upon mint.
The mint function takes in an amount to mint and a corresponding number of uri in an array.
Then as the tokenID is increased, the current tokenID is is set to the uri in the array.
User mints 2 items, token id of first item is 1 and second item is 2
The uri given as [uri1, uri2] is then assigned, 1-uri1 and 2 to uri2
When the tokenURI is called, with tokenID argument, instead of concatenating a base uri with tokenid, it returns the uri from the mapping perhaps.
(Id=> uri) token
TokenURI(1) returns
token[1] =uri1

I wonder if it’s possible with the current protocols in place for ERC721

Thank you for your opinions

This should be possible and relatively simple to put together. You should give this a try and write the mint function that you need, then share it here.

Take a look at Contracts Wizard and choose Mintable + URI Storage. You should change safeMint so it takes an array of URIs instead of a single one, and then inside the function add a for loop.

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

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

contract MyToken is ERC721, ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    uint256 _tokenId = 1;

    constructor() ERC721("MyToken", "MTK") {}

    function safeMint(address to, string[] memory uri) public onlyOwner {
         for(uint8 i = 0; i<uri.length; i++){
        _tokenId = _tokenIdCounter.current();      
        _safeMint(to, _tokenId);
        _setTokenURI(_tokenId, uri[i]);

    // The following functions are overrides required by Solidity.

    function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {

    function tokenURI(uint256 tokenId)
        override(ERC721, ERC721URIStorage)
        returns (string memory)
        return super.tokenURI(tokenId);

Thank you so much!
this works pretty well, although I tried to initialize the token id as one, so the counter starts from one but it doesn't work that way. Is there a way i can make it start counting from 1?

Include _tokenIdCounter.increment() in the constructor.

1 Like

It works! I'll give you credit in the smart contract comment haha. Thank you so much

1 Like

I have written something similar but it does not work, does anyone has any insights?

I have added a bulkMint function which takes a string array and in the function I will iterate the string array and call safeMint multiple times.
I found that if I pass a string array with only 1 element, it works. However, if there are more than 1 strings, it throws error.
Succeeded tx:
Failed tx:

function bulkMint(address to, string[] memory uris)
        for (uint8 i = 0; i < uris.length; i++) {
            safeMint(to, uris[i]);

    function safeMint(address to, string memory uri)
        uint256 tokenId = _tokenIdCounter.current();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);

my contract:

That error looks really strange! The code looks good to me. Have you tested it locally with Hardhat?

Shouldn't the safeMint() be internal, if it only gets called by bulkMint?

(I'm a newbie, so don't take my words for the truth!)

Have you found an answer? I'm trying to mint more than one NFT in one contract (with a list of URIs like you) and trying to figure out how to get it done properly :slight_smile:

In this case it doesn't seem to make a difference. There is no real downside to making it public, only minimal overhead to bytecode size and runtime cost.

1 Like

Thank you for the information :slight_smile: