Overview

This step-by-step tutorial will guide you through the entire process, from building an Angular app to securely storing your artwork on IPFS using Pinata to deploying and minting your very own NFT collection with Thirdweb's powerful NFT Collection smart contract platform.

All the tools and platforms used in this tutorial are free to use. As a developer, you should not incur any costs while following along.

At the end of the tutorial, the NFTs you generate will be visible in a collection on the OpenSea platform.

Before minting, make sure your MetaMask wallet is connected to Sepolia and has test ETH. If you do not have any ETH in your testnet wallet, go to https://sepoliafaucet.com to get free Sepolia ETH for this tutorial.


🔧 Tech Stack & Tools

  • Angular: Frontend framework
  • Pinata: Upload files to IPFS
  • Thirdweb: Web3 SDK + contract hosting
  • MetaMask: Browser wallet / authentication
  • OpenSea: NFT marketplace (Sepolia testnet)

✅ Prerequisites

⚠️ Installation of Node.js and Angular CLI is beyond the scope of this tutorial. It's assumed you already have the appropriate dev tools installed and set up prior to beginning.

  • Node.js v18+
  • Angular CLI: npm install -g @angular/cli
  • MetaMask (connected to Sepolia)
  • Test ETH: https://sepoliafaucet.com
  • Pinata account with JWT token (Web3 SDK section)
  • Thirdweb account

🔹 Step 1: Upload Files to IPFS via Pinata

1.1 Setup Pinata account

  1. Create a Pinata API Key (JWT)

  2. Log into Pinata

  3. Go to "Developers" section → API Keys

  4. Click New Key

  • Name it nft-angular-app
  • Choose "admin" option to give it full permissions
  • After creating it, copy the JWT token — you'll use it in your app.

1.2 Upload Images

  1. Navigate to the Files tab

  2. Manually upload your NFT image assets (JPG, PNG, etc.)

    • Leave privacy setting to Public

⚠️ All image files uploaded under your account will appear in the Angular app, regardless of the project.


🔹 Step 2: Create Thirdweb Project + Contract

  1. Go to https://thirdweb.com/dashboard
  2. Click Create Project and save your:
    • Client ID (frontend-safe)
    • Specify localhost:4200 in the list of accepted domains to limit access
    • Click Create
  3. Deploy a contract:
    • View your new project
    • Navigate to "Contracts"
    • Choose Prebuilt Contract > NFT Collection
    • Fill in name, symbol
    • Ensure contract is choose Sepolia
    • Deploy your contract -> you will be prompted to confirm the transaction using MetaMask
  4. Copy the deployed contract address — you’ll use this in Step 4 when we create our ThirdwebService class.

🔹 Step 3: Angular App Setup

The following commands are cross-platform and should work the same on Windows, macOS, and Linux (provided Node.js and Angular CLI are properly installed). Run each command independantly as you will have some prompting between them.

❗ When prompted, say NO to SSR/Prerendering.
⚠️ When adding material choose Yes to proceed and pick any pre-built theme and say Yes to setting up global typography.

ng new nft-mint-app --standalone --routing --style=scss
cd nft-mint-app
ng add @angular/material
npm install @thirdweb-dev/sdk ethers pinata-web3

🔹 Step 4: Thirdweb Service (src/app/thirdweb.service.ts)

Generate the Angular service:

ng generate service thirdweb --skip-tests

Code for thirdweb.service.ts

⚠️ Be sure to paste the contract address of your Thirdweb NFT contract in the placeholder provided

import { Injectable } from '@angular/core';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { ethers } from 'ethers';

@Injectable({ providedIn: 'root' })
export class ThirdwebService {
  private sdk: ThirdwebSDK | null = null;
  private contractAddress = '0xYourContractAddressHere';

  private async ensureSdk() {
    if (typeof window === 'undefined' || !(window as any).ethereum) {
      throw new Error('MetaMask is not available.');
    }
    await (window as any).ethereum.request({ method: 'eth_requestAccounts' });
    if (!this.sdk) {
      const provider = new ethers.providers.Web3Provider((window as any).ethereum);
      const signer = provider.getSigner();
      this.sdk = new ThirdwebSDK(signer, {
        clientId: '[THIRD-WEB-CLIENT-ID]', // Frontend-safe       
      });           
    }
  }

  async mintTo(walletAddress: string, metadata: { name: string; description: string; image: string }) {
    await this.ensureSdk();
    const contract = await this.sdk!.getContract(this.contractAddress, 'nft-drop');
    return contract.erc721.mintTo(walletAddress, metadata);
  }
}

🔹 Step 5: Add NFT Minting Logic to AppComponent

Replace the contents of src/app/app.component.html

NFT Minting App

  Mint Your NFT

  
    This app allows you to mint your own NFT from an image that you’ve uploaded to IPFS using Pinata.
    It uses Thirdweb’s prebuilt ERC721 Drop contract and performs a lazy minting process.
    Thirdweb makes it simple to interact with Web3 and smart contracts using their SDK.
    Once minted, your NFT will be visible on OpenSea's Sepolia testnet.
  

  
    To get started, connect your MetaMask wallet using the button below.
    Your wallet must be connected to view available images and mint NFTs.
  

  
    Connect Wallet
  

  
    
      
        
      
      
        
          {{ file.name || file.cid }}
        
      
      
        {{ mintedCids.has(file.cid) ? 'Minted' : 'Mint NFT' }}
      
    
  
  🎉 NFT successfully minted!

Replace the contents of src/app/app.component.ts:

⚠️ Make sure to paste in your Pinata gateway and JWT token from Step 1 in the placeholders provided in the code below

import { Component, OnInit } from '@angular/core';
import { ThirdwebService } from './thirdweb.service';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { NgIf, NgFor } from '@angular/common';
import { PinataSDK } from 'pinata-web3';

interface Web3File {
  cid: string;
  name: string | null;
  metadataCid: string | null
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MatToolbarModule, MatButtonModule, NgIf, NgFor],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit {
  pinataConfig = {
    pinataGateway: 'https://[PINATA GATEWAY URL]',
    pinataJwt: '[PINATA JWT]'
  };
  pinataGateway = this.pinataConfig.pinataGateway + "/ipfs/";
  minting = false;
  success = false;
  uploadedFiles: Web3File[] = [];
  mintedCids = new Set();
  walletConnected = false;

  pinata = new PinataSDK({ pinataJwt: this.pinataConfig.pinataJwt });

  constructor(private tw: ThirdwebService) {}

  async ngOnInit() {
    if (typeof window !== 'undefined' && (window as any).ethereum && (window as any).ethereum.selectedAddress) {
      this.walletConnected = true;
    }

    const list = await this.pinata.listFiles();

    this.uploadedFiles = list.map(file => ({
      cid: file.ipfs_pin_hash,
      name: file.metadata.name ?? '',
      metadataCid: null
    }));
  }

  async loadUploadedFiles() {
    const files = await this.pinata.listFiles() as any[]; // You can define a better type later
    this.uploadedFiles = files.map(meta => {

      return {
        name: meta.metadata.name,
        cid: meta.metadata.ipfs_pin_hash,
        metadataCid: meta.ipfs_pin_hash      
      } as Web3File;
    });
  }

  connectWallet() {
    if (typeof window !== 'undefined' && (window as any).ethereum) {
      (window as any).ethereum.request({ method: 'eth_requestAccounts' })
        .then(() => {
          this.walletConnected = true;
          console.log('Wallet connected');
        })
        .catch((error: any) => console.error('User denied wallet connection:', error));
    } else {
      alert('MetaMask is not installed.');
    }
  }

  async mintNft(cid: string, name: string |null) {
    this.success = false; //reset success flag
    this.minting = true;
    const metadata = {
      name: `RedRock NFT: ${name}`,
      description: 'Minted via Angular + Thirdweb',
      image: `ipfs://${cid}`
    };
    try {
      const accounts = await (window as any).ethereum.request({ method: 'eth_accounts' });
      const wallet = accounts[0];
      const tx = await this.tw.mintTo(wallet, metadata);
      console.log('Minted to wallet:', tx);
      this.mintedCids.add(cid);
      this.success = true;
    } catch (err) {
      console.error('Error minting:', err); 
    }
    this.minting = false;
  }
}

Add a little style to your page:
Replace the contents of src/app/app.component.scss

.container {
    padding: 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 80vh;
    max-width: 600px;
    margin: 0 auto;
    line-height: 1.6;
    text-align: left;
  }

  .container .title {
    text-align: center;
  }

🔹 Step 6: Start Your Angular App

Once everything is configured, it’s time to start your Angular app and try it out:

ng serve

Then open your browser to: http://localhost:4200

You should now see the NFT Minting App interface.

🔹 Step 6.5: Minting UI Behavior

When the app loads for the first time, you’ll see an empty page with basic instructions.

Click 'Connect Wallet' button to authenticate via MetaMask. Once connected, the app will list your uploaded metadata files from Pinata.

Each listed item will:

  • Show the NFT name
  • Display a thumbnail preview
  • Include a 'Mint NFT' button beside it

Clicking Mint NFT button will:

  • Use the selected metadata file's CID
  • Pass the CID to the Thirdweb service, which interacts with your deployed contract
  • Trigger MetaMask to prompt you to confirm the minting transaction

The minted NFT will be stored on the Sepolia testnet and assigned to your connected wallet address.

Because the NFTs are authored using your Metamask wallet address, you are able to verify the NFT being created by looking in the Thirdweb dashboard and finding the NFTs in the project you created in Step 2.


🔹 Step 7: View NFT on OpenSea

  1. Visit https://testnets.opensea.io
  2. Connect your MetaMask wallet by clicking the wallet icon in the top right
  3. Make sure you're on the Sepolia Testnet — you may need to go to MetaMask settings and enable Show test networks to see Sepolia
  4. Search your wallet address or visit your profile to view the minted NFTs
  5. Your NFT may take a minute or two to appear...just be patient.

✅ You Did It!

You've built an NFT minting dApp with:

  • 📦 IPFS image + metadata hosting (Pinata)
  • ⚙️ Smart contract (Thirdweb)
  • 🔐 Wallet minting (MetaMask)
  • 🖼 NFT viewing (OpenSea testnet)

🔗 Resources

  • Thirdweb Dashboard
  • Pinata Web3 SDK
  • Thirdweb Docs
  • OpenSea Testnet
  • Sepolia Faucet