Minting with thirdweb
thirdweb is a platform that offers tools and infrastructure for building decentralized applications on multiple blockchain networks. It simplifies blockchain integration with SDKs, APIs, and pre-built components.
In the case of NFT minting, thirdweb offers a polished dashboard and IPFS upload along with its audited contracts and extensive SDKs.
Minting a single NFT manually
If you just want to upload a single image and mint it, thirdweb offers a click-to-deploy interface.
First, create a thirdweb account using your Web3 wallet as the owner. This gives you access to your thirdweb dashboard.
From there, you can choose between two paths:
Use their TokenERC721 page and its "Deploy Now" button.
Or use their dashboard's "Create Token" interface.
Let's explore each real quick — the thirdweb Developer Portal will give you more information should you need it.
Using the TokenERC721 page
Go to the TokenERC721 page. This relies on their contract for creating an NFT Collection.
Click "Deploy Now" to access to contract deployment page.

Fill in the "Contract metadata" section:
Upload the image that you want to represent your NFT collection (or just the image that you want to mint).
Give it a unique name and a symbol/ticker to your collection.
Give it a description.
thirdweb automatically fills the recipient addresses for "Primary Sales" and "Royalties", make sure to check that the Web3 addresses are indeed that of your wallet.
In the "Deploy Options", choose Chiliz Chain (or Chiliz Spicy Testnet if this is just a test) and click "Deploy Now":

thirdweb will start deploying the contract, triggered a confirmation modal from your Web3 wallet. Confirm it to finish deploying.
Once done, click the "View Contract" button. You'll be taken to the contract's page on your thirdweb dashboard, with a checklist/progress bar:
As you can see from the checklist, for now all you've done is deploy a contract, you still need to actually mint the NFT. On the left column, click on the "NFTs" menu item to open a new page, which is for now empty:
Click the "Mint" button and a side-panel opens with field for each attribute of your NFT. This is were you create the metadata for your NFT:
Fill in the various fields. You can create fields that are specific to your NFT collection, such as "Artist_name" or "Type_of_work", and explore advanced options, but for this test you can keep it minimal: name, media file, description.
Click on the "Mint NFT" button at the bottom of the side-panel, and approve the transaction from your wallet.
thirdweb will then display the "NFTs" page, with your NFT now visible, attached to your NFT contract.
DONE! You have minted your NFT!
To check that it is indeed on Chiliz Chain, open Chiliscan (or its Testnet variant if you're using Spicy Testnet) and copy-paste the NFT's hash in the search field. It should straight away confirm that your contract is on the chain, and its "Inventory" tab should list your NFT with you (or at least your wallet's ID) as the owner.

If you minted your NFT on Chiliz Chain Mainnet, you should see it appear in your account on marketplaces such as Rarible or OpenSea — and from there, you can start selling it!
Using the "Create Token" button
This is in fact a more straightforward way than the one above, and is a recent addition to thirdweb.
From any project folder in your thirdweb dashboard, click the "Tokens" option in the left sidebar:
In the "Tokens" page that open, click the "Create Token" button on the right handside. A modal window open with 2 options, choose "Create NFT Collection".
This will take you to a step-by-step version of the TokenERC721 deployment page (in effect, it will deployer an ERC-721 Drop contract). Fill in all the fields necessary for this contract, then press the "Next" button.
The next screen is about the NFT itself: upload the image and fill in the fields describing your image, then press the "Next" button.
thirdweb now displays a summary of the NFT collection that you are about to launch. Check that everything is correct then click the "Launch NFT Collection" button.
thirdweb takes charge of deploying the contract, minting the NFT, and setting the conditions, all in one shot. You will need to confirm three transactions through your Web3 wallet.
Once this is done, click the "View NFT" button, and you'll be taken back to your thirdweb dashboard, displaying the page specific to this contract — and its attached NFTs.
From there, you can check that it's indeed on the chain through a block explorer, or start selling your NFT through a marketplace.
Minting a single NFT programmatically
Here is a code sample that using the thirdweb v5 SDK, which uploads the media file to IPFS, generates the metadata.json
file, then uploads it to IPFS too.
This will require you to have:
An already-deployed ERC-721 contract on Chiliz Chain. In this case you should do it using thirdweb rather another tool.
A thirdweb API key, tied to the ERC-721 contract that you deployed through thirdweb. You can find it in the project page of the smart contract, under the name "Client ID".
A local installation of the thirdweb SDK. You can do it using
npm
:
npm install thirdweb dotenv
The dotenv
parameter generates an .env
file, which is necessary to store private values:
THIRDWEB_SECRET_KEY=YOUR_TW_SECRET_KEY # From your thirdweb project's page.
PRIVATE_KEY=0xabc... # The Web3 wallet that will send the transaction.
CONTRACT_ADDRESS=0xYour721Address # Your deployed TokenERC721 contract.
CHAIN_ID=88882 # 88888=Mainnet, 88882=Spicy
IMAGE_PATH=./art/image.png # The local path to your media file.
NAME=My Chiliz NFT
DESCRIPTION=Minted on Chiliz Chain
Never expose your PRIVATE_KEY
nor THIRDWEB_SECRET_KEY
in client-side code!
Keep this file on a secure server/CI, and don't upload it on a public repositoryç
Now that you're all set, you can take inspiration from this script, which will:
Upload the media file to IPFS
Build and upload the
metadata.json
fileMint the NFT using the contract's mintTo method.
Display the resulting
tokenId
andtokenUrl
to you.
This is just a sample code, you will need to adapt it to your own project code!
import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import { createThirdwebClient, getContract, sendTransaction, waitForReceipt, parseEventLogs } from "thirdweb";
import { defineChain } from "thirdweb/chains";
import { privateKeyToAccount } from "thirdweb/wallets";
import { mintTo, transferEvent, getNFT } from "thirdweb/extensions/erc721";
import { upload } from "thirdweb/storage";
function guessMime(p: string) {
const ext = path.extname(p).toLowerCase();
if (ext === ".png") return "image/png";
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
if (ext === ".webp") return "image/webp";
if (ext === ".gif") return "image/gif";
if (ext === ".mp4") return "video/mp4";
if (ext === ".webm") return "video/webm";
return "application/octet-stream";
}
async function main() {
const client = createThirdwebClient({ secretKey: process.env.THIRDWEB_SECRET_KEY! });
const chain = defineChain(Number(process.env.CHAIN_ID)); // 88888 or 88882
const account = privateKeyToAccount({ client, privateKey: process.env.PRIVATE_KEY! });
const contract = getContract({
client,
chain,
address: process.env.CONTRACT_ADDRESS as `0x${string}`,
});
// Upload media file to IPFS
const filePath = process.env.IMAGE_PATH!;
const buffer = fs.readFileSync(filePath);
const file = new File([buffer], path.basename(filePath), { type: guessMime(filePath) });
const [imageUri] = await upload({ client, files: [file] }); // returns ipfs://...
// console.log("imageUri:", imageUri);
// Build & upload metadata.json referencing IPFS imageUri
const metadata = {
name: process.env.NAME!,
description: process.env.DESCRIPTION!,
image: imageUri
};
const metaBlob = new Blob([JSON.stringify(metadata, null, 2)], { type: "application/json" });
const metaFile = new File([metaBlob], "metadata.json", { type: "application/json" });
const [metadataUri] = await upload({ client, files: [metaFile] });
// console.log("metadataUri:", metadataUri);
// Prepare & send mintTo transaction using the metadata URI
// mintTo accepts a string to use directly as tokenURI.
const tx = mintTo({
contract,
to: account.address,
nft: metadataUri, // The tokenURI
});
const { transactionHash } = await sendTransaction({ transaction: tx, account });
//console.log("tx:", transactionHash);
const receipt = await waitForReceipt({ client, chain, transactionHash });
// Parse Transfer event to display tokenId
const events = parseEventLogs({ logs: receipt.logs, events: [transferEvent()] });
const minted = events.find(e => e.eventName === "Transfer" && e.args.from === "0x0000000000000000000000000000000000000000");
const tokenId = minted ? (minted.args.tokenId as bigint) : undefined;
//console.log("tokenId:", tokenId?.toString() ?? "(not found)");
if (tokenId !== undefined) {
const nft = await getNFT({ contract, tokenId });
//console.log("tokenURI (read back):", nft.tokenURI);
}
}
main().catch((err) => (console.error(err), process.exit(1)));
The important parts to take inspiration from are:
upload
stores files (and JSON) to IPFS and returnsipfs://…
URIs.mintTo
accepts a string to use directly astokenURI
.We parse the standard ERC-721
Transfer
event to read the mintedtokenId
.
Minting a collection of NFTs programmatically
Minting a collection of NFTs is not that different from minting a single one. The most notable difference is that your .env
file points to the folder that contains all the images using IMAGE_DIR
, rather than on a single image with IMAGE_PATH
.
THIRDWEB_SECRET_KEY=YOUR_TW_SECRET_KEY # From your thirdweb project's page.
PRIVATE_KEY=0xabc... # The Web3 wallet that will send the transaction.
CONTRACT_ADDRESS=0xYour721Address # Your deployed TokenERC721 contract.
CHAIN_ID=88882 # 88888=Mainnet, 88882=Spicy
IMAGE_PATH=./art # The local path to your media files.
NAME_PREFIX=My Chiliz NFT # This will become: "My Chiliz NFT <n>"
DESCRIPTION=Minted on Chiliz Chain
The sample code that we represents here therefore takes this into account:
import 'dotenv/config';
import fs from 'fs';
import path from 'path';
import { createThirdwebClient, getContract, sendTransaction, waitForReceipt, parseEventLogs } from "thirdweb";
import { defineChain } from "thirdweb/chains";
import { privateKeyToAccount } from "thirdweb/wallets";
import { mintTo, transferEvent, getNFT } from "thirdweb/extensions/erc721";
import { upload } from "thirdweb/storage";
function guessMime(p: string) {
const ext = path.extname(p).toLowerCase();
if (ext === ".png") return "image/png";
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
if (ext === ".webp") return "image/webp";
if (ext === ".gif") return "image/gif";
if (ext === ".mp4") return "video/mp4";
if (ext === ".webm") return "video/webm";
return "application/octet-stream";
}
// One way to gather all files
function listMediaFiles(dir: string) {
const allow = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".mp4", ".webm", ".svg"]);
return fs
.readdirSync(dir)
.filter((f) => allow.has(path.extname(f).toLowerCase()))
.map((f) => path.join(dir, f));
}
async function main() {
const client = createThirdwebClient({ secretKey: process.env.THIRDWEB_SECRET_KEY! });
const chain = defineChain(Number(process.env.CHAIN_ID)); // 88888 or 88882
const account = privateKeyToAccount({ client, privateKey: process.env.PRIVATE_KEY! });
const contract = getContract({
client,
chain,
address: process.env.CONTRACT_ADDRESS as `0x${string}`,
});
// Upload media files in a folder to IPFS
const dirPath = process.env.IMAGES_DIR!;
const filePaths = listMediaFiles(dirPath);
if (!filePaths.length) {
throw new Error(`No media files found in ${dirPath}`);
}
const fileObjs = filePaths.map((p) => {
const buffer = fs.readFileSync(p);
return new File([buffer], path.basename(p), { type: guessMime(p) });
});
const imageUris = await upload({ client, files: fileObjs }); // returns ["ipfs://...", ...]
//console.log("Uploaded images to IPFS:", imageUris.length, "files");
// Build & upload each metadata.json referencing its imageUri
// Uses NAME and DESCRIPTION envs; names become "<NAME> #<index>"
const namePrefix = process.env.NAME!;
const description = process.env.DESCRIPTION!;
// Create an array of File objects for metadata JSONs
const metaFiles = imageUris.map((imageUri, i) => {
const metadata = {
name: `${namePrefix} #${i + 1}`,
description,
image: imageUri
};
const metaBlob = new Blob([JSON.stringify(metadata, null, 2)], { type: "application/json" });
return new File([metaBlob], `metadata-${i + 1}.json`, { type: "application/json" });
});
const metadataUris = await upload({ client, files: metaFiles }); // ["ipfs://.../metadata-1.json", ...]
//console.log("Uploaded metadata files to IPFS:", metadataUris.length);
// Prepare & send mintTo transactions using the metadata URIs (sequential)
const ZERO = "0x0000000000000000000000000000000000000000";
for (let i = 0; i < metadataUris.length; i++) {
const metadataUri = metadataUris[i];
const tx = mintTo({
contract,
to: account.address,
nft: metadataUri, // The tokenURI
});
const { transactionHash } = await sendTransaction({ transaction: tx, account });
console.log(`[${i + 1}/${metadataUris.length}] tx:`, transactionHash);
const receipt = await waitForReceipt({ client, chain, transactionHash });
// Parse Transfer event to get tokenId for this mint
const events = parseEventLogs({ logs: receipt.logs, events: [transferEvent()] });
const minted = events.find(
(e) => e.eventName === "Transfer" && typeof e.args.from === "string" && e.args.from.toLowerCase() === ZERO
);
const tokenId = minted ? (minted.args.tokenId as bigint) : undefined;
// console.log(`[${i + 1}/${metadataUris.length}] tokenId:`, tokenId?.toString() ?? "(not found)");
if (tokenId !== undefined) {
const nft = await getNFT({ contract, tokenId });
// console.log(`[${i + 1}/${metadataUris.length}] tokenURI (read back):`, nft.tokenURI);
}
}
}
main().catch((err) => (console.error(err), process.exit(1)));
The detailed changes are:
IMAGES_DIR
replacesIMAGE_PATH
.Added
listMediaFiles
to gather multiple inputs.Batched
upload
for images and for metadata (keeps order).Loop to
mintTo
eachmetadataUri
, parsing eachTransfer
event for itstokenId
.Names are auto-numbered:
NAME #1
,NAME #2
, … using your existingNAME
/DESCRIPTION
.
Again, please use this as an inspiration for your own code, do not use as-is!
Note: If you prefer to do lazy minting, thirdweb's ERC-721 contract has a lazyMint
method.
Last updated
Was this helpful?