Skip to main content
Learn everything about non-fungible tokens (NFTs) on NEAR by building a fully-featured NFT smart contract from the ground up.

Overview

This comprehensive tutorial series takes you from deploying a pre-built NFT contract to implementing every standard extension. By the end, you’ll have a production-ready NFT contract.

NFT Tutorial Series

Complete step-by-step NFT tutorial

What you’ll learn

NEP-171 Core

The foundational NFT standard for minting and transferring tokens

NEP-177 Metadata

Add metadata like names, descriptions, and images to NFTs

NEP-178 Approval

Allow marketplaces to transfer NFTs on behalf of owners

NEP-181 Enumeration

Query NFTs by owner, paginate results, and more

NEP-199 Royalties

Implement creator royalties for secondary sales

NFT Events

Emit events for indexers and frontends to track

Prerequisites

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Install cargo-near
cargo install cargo-near

# Install NEAR CLI
curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/near/near-cli-rs/releases/latest/download/near-cli-rs-installer.sh \
  | sh

# Create testnet account
near account create-account sponsor-by-faucet-service your-name.testnet \
  autogenerate-new-keypair save-to-keychain network-config testnet create
New to Rust? Start with the smart contract quickstart to learn the basics.

Tutorial steps

The tutorial is divided into progressive chapters:
1

Pre-deployed contract

Start by minting NFTs using an already-deployed contract. No coding required!Begin tutorial →
2

Contract architecture

Learn the structure of an NFT contract and compile your first version.View chapter →
3

Minting NFTs

Implement the minting function to create new NFTs.View chapter →
4

Contract upgrades

Learn how to upgrade deployed contracts while preserving state.View chapter →
5

Enumeration

Add functions to query NFTs by owner and paginate results.View chapter →
6

Core standard

Implement transfers and the NEP-171 core standard.View chapter →
7

Events

Emit events for minting and transfers.View chapter →
8

Approvals

Allow marketplaces to transfer NFTs on your behalf.View chapter →
9

Royalties

Add royalty payments for secondary sales.View chapter →
10

Marketplace

Build a marketplace to buy and sell NFTs.View chapter →

Quick start example

Here’s a minimal NFT contract structure:
use near_sdk::{
    near, AccountId, PanicOnDefault,
    collections::{LazyOption, LookupMap, UnorderedSet},
};
use near_sdk::json_types::U128;

#[near(serializers = [json, borsh])]
pub struct Token {
    pub token_id: String,
    pub owner_id: AccountId,
    pub metadata: TokenMetadata,
}

#[near(serializers = [json, borsh])]
pub struct TokenMetadata {
    pub title: Option<String>,
    pub description: Option<String>,
    pub media: Option<String>,
    pub media_hash: Option<String>,
}

#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct NftContract {
    pub owner_id: AccountId,
    pub tokens_per_owner: LookupMap<AccountId, UnorderedSet<String>>,
    pub tokens_by_id: LookupMap<String, Token>,
    pub token_metadata_by_id: LookupMap<String, TokenMetadata>,
}

#[near]
impl NftContract {
    #[init]
    pub fn new(owner_id: AccountId) -> Self {
        Self {
            owner_id,
            tokens_per_owner: LookupMap::new(b"o"),
            tokens_by_id: LookupMap::new(b"t"),
            token_metadata_by_id: LookupMap::new(b"m"),
        }
    }
    
    #[payable]
    pub fn nft_mint(
        &mut self,
        token_id: String,
        receiver_id: AccountId,
        metadata: TokenMetadata,
    ) -> Token {
        // Implementation in tutorial
        Token {
            token_id,
            owner_id: receiver_id,
            metadata,
        }
    }
}

NFT standards (NEPs)

The foundation for all NFTs on NEAR. Defines:
  • Token ownership
  • Transfer methods
  • Balance queries
Read the standard
Adds metadata to NFTs:
  • Token title and description
  • Media URLs (images, videos)
  • Contract-level metadata
Read the standard
Enables marketplace functionality:
  • Approve accounts to transfer on your behalf
  • Revoke approvals
  • Query approved accounts
Read the standard
Query functions for NFTs:
  • Get all tokens for an owner
  • Paginate results
  • Get total supply
Read the standard
Automatic royalty payments:
  • Set royalty percentage
  • Define multiple beneficiaries
  • Paid on marketplace sales
Read the standard

Testing your NFT contract

Use NEAR Workspaces to test:
#[tokio::test]
async fn test_nft_mint() -> Result<()> {
    let worker = near_workspaces::sandbox().await?;
    let contract = worker.dev_deploy(include_bytes!("../target/near/nft.wasm")).await?;
    
    // Initialize contract
    contract.call("new")
        .args_json(serde_json::json!({
            "owner_id": contract.id()
        }))
        .transact()
        .await?;
    
    // Mint an NFT
    let outcome = contract
        .call("nft_mint")
        .args_json(serde_json::json!({
            "token_id": "1",
            "receiver_id": contract.id(),
            "metadata": {
                "title": "Test NFT",
                "description": "A test NFT",
                "media": "https://example.com/nft.png"
            }
        }))
        .deposit(near_sdk::NearToken::from_millinear(10))
        .transact()
        .await?;
    
    assert!(outcome.is_success());
    
    // Query the NFT
    let token: Token = contract
        .view("nft_token")
        .args_json(serde_json::json!({"token_id": "1"}))
        .await?
        .json()?;
    
    assert_eq!(token.metadata.title, Some("Test NFT".to_string()));
    
    Ok(())
}

Common use cases

Digital art

Create and sell unique digital artwork with royalties

Gaming items

In-game assets that players truly own

Collectibles

Trading cards, sports memorabilia, and more

Memberships

Token-gated communities and access passes

Tickets

Event tickets with verifiable authenticity

Certificates

Credentials, diplomas, and proof of achievement

JavaScript NFT example

Prefer JavaScript? Check out the JavaScript NFT implementation:
import { NearBindgen, near, call, view, UnorderedMap } from 'near-sdk-js';

@NearBindgen({})
class NftContract {
  constructor() {
    this.owner_id = '';
    this.tokensById = new UnorderedMap('t');
  }
  
  @call({ payableFunction: true })
  nft_mint({ token_id, receiver_id, metadata }) {
    const token = {
      token_id,
      owner_id: receiver_id,
      metadata
    };
    
    this.tokensById.set(token_id, token);
    return token;
  }
  
  @view({})
  nft_token({ token_id }) {
    return this.tokensById.get(token_id);
  }
}
View complete JS example →

Next steps

Start the tutorial

Begin building your NFT contract

NFT primitives

Learn about using existing NFT contracts

Auction tutorial

Build an NFT auction platform

FT tutorial

Learn about fungible tokens

Build docs developers (and LLMs) love