- On Sui, every on-chain object is already unique and owned — so making an NFT means writing one struct with a
UIDfield and thehas keyability. - You need the Sui CLI, a free Testnet wallet, and a text editor — no paid tools required.
- The full workflow is: write your Move module → build → publish → mint → transfer, all from the command line.
- The Object Display Standard controls how your NFT looks in wallets and marketplaces — skip it and your NFT will show up blank.
- Chialisp developers will feel right at home: Sui’s object ownership model maps closely to Chia’s coin/puzzle model, just with different syntax.
This sui nft tutorial shows you how to write, deploy, mint, and transfer a non-fungible token on the Sui blockchain using the Move programming language. Sui treats every on-chain object as a unique, owned asset by default, which means creating a true NFT takes far less boilerplate than you’d write in Solidity or even Chialisp.
What Is a Sui NFT — and Why It Feels Different
If you’ve mined Chia or dipped into Ethereum, you already know the usual NFT story: a smart contract keeps a big list that maps token IDs to wallet addresses. When someone buys an NFT, the contract updates that list. The NFT itself doesn’t really “live” anywhere — it’s just a number in a ledger.
Sui flips this completely. On Sui, every on-chain object is a distinct, self-contained asset with its own globally unique ID and its own owner — which means every Sui object is, in technical terms, already an NFT.[1] There is no master registry. The token isn’t an entry in a mapping; it’s an independent object that gets moved around the blockchain the same way a file moves between folders on your computer.
The Object Model: Key and Store Abilities
In Move, every data type has “abilities” that control what the runtime can do with it. For an NFT you need two: key and store. The key ability gives the struct a globally unique UID and lets it live as a top-level Sui object. The store ability allows the object to be freely transferred using the standard Sui framework function transfer::public_transfer.[2] Without store, the object becomes soulbound — it can only be moved by custom logic you write inside your own module, which is actually a useful feature for achievement badges or non-tradeable items.
Todd Nowacki, a Move engineer at Mysten Labs who worked on the language almost from its inception, summed it up plainly in a public AMA:
“Resources, assets and tokens are first-class baked things on Move. It’s more than just the word asset — we have objects that are pretty much NFTs, even your coins are NFTs if you actually care about the idea of that coin.”
— Todd Nowacki, Move Engineer, Mysten Labs[6]
How Chialisp Developers Will Recognize This Pattern
If you’ve spent time writing Chialisp smart contracts on Chia Network, the Sui object model will feel surprisingly familiar. In Chialisp, a coin is the base unit — it has a puzzle (logic) and a solution (input), and it lives at a unique coin ID derived from its parent and amount. No coin is ever the same object as another. Sui’s object model works the same way philosophically: each object has a unique UID, an owner, and a type that defines its behavior. The big difference is syntax and tooling, not concept. Where Chialisp thinks in puzzles and solutions, Move thinks in structs and entry functions — but both treat assets as first-class, independent things rather than rows in a shared table.
| Goal | Best Approach | Key Move Feature |
|---|---|---|
| Basic collectible, freely tradeable | Struct with has key, store + public_transfer | Transfer module |
| Achievement badge, non-transferable | Struct with has key only (no store) | Soulbound pattern |
| Marketplace-ready with royalties | Struct + Kiosk + TransferPolicy | Kiosk standard |
| In-game item with upgradeable stats | Struct + Dynamic Fields | sui::dynamic_field |
| Rental / time-limited access | NFT Rental via Kiosk Apps | ERC-4907-style Kiosk App |
Set Up Your Sui Development Environment
Before you write a single line of Move, you need three things on your machine: the Sui CLI, a Testnet address, and free SUI tokens for gas. The whole setup takes under 15 minutes.
Install the Sui CLI
Sui requires Rust and Cargo, so start there if you don’t have them. On macOS or Linux, one command handles it:
curl --proto '=https' --tlsv1.2 -sSf //sh.rustup.rs | sh
Once Rust is installed, follow the official Sui installation guide at docs.sui.io/guides/developer/getting-started/sui-install[3] to install Sui binaries for your OS. After installation, confirm it worked:
sui --version
You should see a version number printed back. If you see nothing, check that your Cargo bin path is in your system PATH.
Connect to Testnet and Get Free SUI
Run sui client in your terminal for the first time. It will walk you through connecting to a full node and generating a new key pair. Choose Testnet when prompted. After setup, fund your wallet using the built-in faucet command:
sui client faucet
This drops free Testnet SUI into your wallet so you can pay gas fees while you practice. Check your balance with:
sui client balance
You’re ready to build once you see a non-zero balance.
Write Your NFT Smart Contract in Move
Create a new Move package from the command line:
sui move new my_nft
This creates a folder called my_nft with a Move.toml file and a sources/ folder. Open sources/ and create a new file called collection.move. Everything from here goes into that file.
Define the NFT Struct
The struct is the blueprint for your NFT. It defines what data each token holds. A standard Sui NFT needs a UID as its first field — this is the globally unique identifier Sui assigns at mint time. Here’s the full module with the struct, events, and view functions:
module my_nft::collection {
use std::string;
use sui::url::{Self, Url};
use sui::event;
use sui::transfer;
use sui::tx_context::{Self, TxContext};
/// The NFT object — anyone can mint one
public struct MyNFT has key, store {
id: UID,
name: string::String,
description: string::String,
url: Url,
}
/// Event emitted when an NFT is minted
public struct NFTMinted has copy, drop {
object_id: ID,
creator: address,
name: string::String,
}
/// Read-only getters
public fun name(nft: &MyNFT): &string::String { &nft.name }
public fun description(nft: &MyNFT): &string::String { &nft.description }
public fun url(nft: &MyNFT): &Url { &nft.url }
}
Notice that has key, store after the struct name. That’s all it takes to make this a freely transferable NFT object on Sui. Compare this to Ethereum, where you’d inherit from ERC-721, implement six functions, and manage a token ID counter in a mapping. On Sui, ownership and uniqueness are built into the object layer itself — your struct just has to opt in.
Add the Mint Function
Now add the mint function below the getters inside the same module. This function creates a new MyNFT object and sends it straight to whoever called the function:
/// Mint a new NFT to the caller's address
#[allow(lint(self_transfer))]
public fun mint_to_sender(
name: vector<u8>,
description: vector<u8>,
url: vector<u8>,
ctx: &mut TxContext,
) {
let sender = ctx.sender();
let nft = MyNFT {
id: object::new(ctx),
name: string::utf8(name),
description: string::utf8(description),
url: url::new_unsafe_from_bytes(url),
};
event::emit(NFTMinted {
object_id: object::id(&nft),
creator: sender,
name: nft.name,
});
transfer::public_transfer(nft, sender);
}
The object::new(ctx) call is where Sui generates the unique ID. Every single call to this function produces an NFT with a different UID — that uniqueness is enforced at the protocol level, not by your code counting numbers. This is the same philosophical guarantee that Chia’s coin model provides: no two coins share an ID because the ID is derived from cryptographic inputs that are always unique.
Add the Transfer Function
The transfer function is simple — it accepts an NFT object and a recipient address, and hands ownership over:
/// Transfer an NFT to a new owner
public fun transfer(
nft: MyNFT,
recipient: address,
_: &mut TxContext,
) {
transfer::public_transfer(nft, recipient)
}
}
Close the module with the } at the end. Your full collection.move file should now have the struct, both events, the view functions, the mint function, and the transfer function — all inside the module my_nft::collection block.
Build, Publish, and Mint Your NFT
With the contract written, it’s time to put it on-chain. This is a three-step process: build, publish, and then call the mint function.
Build and Check for Errors
From the root of your my_nft folder, run:
sui move build
This compiles the Move source and checks for type errors. If you see any errors, they’ll point to the line number and tell you exactly what’s wrong. Fix them before moving on. A clean build prints something like BUILDING my_nft with no red text.
Publish the Package to Testnet
Deploy the package with a gas budget large enough to cover the transaction:
sui client publish --gas-budget 100000000
Sui will print a long block of output. Look for the line that says PackageID — copy that value. It looks like a long hex string starting with 0x. You’ll use it for every future call to your contract. Save it somewhere, or paste it into a variable in your terminal:
export PACKAGE_ID=0xYOUR_PACKAGE_ID_HERE
Mint Your First NFT
Now call the mint_to_sender function using the Sui CLI. Replace $PACKAGE_ID with your actual package ID:
sui client call \
--package $PACKAGE_ID \
--module collection \
--function mint_to_sender \
--args "My First NFT" "A test NFT on Sui" "//example.com/nft.png" \
--gas-budget 10000000
The transaction output will include an Object ID for the newly minted NFT. That’s your token’s unique address on Sui. You can look it up on the Sui Explorer (suiexplorer.com) and see the name, description, and url fields stored directly on-chain.
Transfer the NFT to Another Address
First, create a second wallet address to receive the transfer:
sui client new-address
Save the new address, then call the transfer function. Replace the object ID and recipient address with your actual values:
sui client call \
--package $PACKAGE_ID \
--module collection \
--function transfer \
--args "0xYOUR_NFT_OBJECT_ID" "0xRECIPIENT_ADDRESS" \
--gas-budget 10000000
After the transaction confirms, check the recipient’s wallet — the NFT object should now appear under their address. The original minting address no longer owns it. This is a direct ownership transfer, not an approval or delegation like you’d write in Ethereum’s ERC-721. The object physically moved.
The Object Display Standard: Make Your NFT Visible
Right now, your NFT exists and transfers correctly — but wallets and explorers won’t know how to render it. They’ll see an object with some fields but won’t know which field is the name, which is the image, or who made it. That’s where the Object Display Standard comes in.
Why Display Matters
The Display<T> object is a template that tells Sui full nodes how to display your NFT type in any app that queries it with showDisplay: true.[2] The standard defines a set of named fields — name, description, image_url, link, project_url, and creator — that wallets like Sui Wallet and explorers like Suiscan read automatically.[5] Without it, your NFT renders as an unknown object. With it, your image shows up, your project name appears, and buyers can click through to your site. Setting up Display requires using a Publisher object in your module’s init function — the official Display guide at docs.sui.io/standards/display walks through the complete pattern with a working Hero module example.
| Feature | Sui Move NFT | Ethereum ERC-721 | Chia NFT1 Standard |
|---|---|---|---|
| Ownership model | Object owned directly by address | Mapping inside contract | Coin owned by puzzle hash |
| Unique ID source | Protocol-generated UID | Developer-tracked tokenId | Coin ID (hash of parent + puzzle) |
| Transfer mechanism | public_transfer (no contract call needed) | transferFrom function on contract | Spend coin with new puzzle |
| Metadata storage | On-chain fields + Display standard | Usually IPFS URI via tokenURI() | On-chain data URI + DID |
| Royalty enforcement | On-chain via Kiosk + TransferPolicy | Optional, off-chain honor system | Royalty % encoded in NFT coin |
| Language | Move (Rust-like) | Solidity | Chialisp (Lisp-like) |
Real-World Case: SoWork Builds Composable NFTs on Sui
SoWork, a virtual co-working startup building a workplace metaverse, chose Sui as its blockchain platform specifically for its ability to produce fast, cost-effective, and composable NFTs. The company’s Mapmaker tool allows creators to mint custom, mutable NFTs — including clothing and furniture items — and transfer them into its metaverse, with on-chain data storage enabling real-time in-world customizations and upgrades.[7] This is the kind of use case Sui’s object model makes straightforward: an NFT that can hold other NFTs and change its own appearance over time, without redeploying the contract.
What to Build Next After This Sui NFT Tutorial
You now have a working, deployed NFT contract on Sui Testnet. You can mint tokens, transfer them between wallets, and verify ownership in the explorer. That’s the foundation every Sui NFT project starts from. From here, the three most practical next steps are: adding the Object Display Standard to make your NFT render correctly in wallets; exploring the Kiosk primitive if you want marketplace functionality with enforced royalties; and looking at the Soulbound NFT pattern if you want tokens that can’t be traded.[4] Each of these extends the same struct you just wrote — Sui’s design makes adding features incremental rather than requiring a full rewrite. If you’ve come from Chia or another chain, you already understand the core insight: ownership is real and direct, assets are first-class, and the protocol does the heavy lifting on uniqueness so you don’t have to.
sui nft tutorial FAQs
What do I need before starting this sui nft tutorial?
Before starting this sui nft tutorial, you need Rust and Cargo installed, the Sui CLI set up and connected to Testnet, and free Testnet SUI tokens from the faucet (sui client faucet). A code editor like VS Code with the Move extension is also highly recommended but not required.
What is the difference between a Sui NFT and an Ethereum ERC-721?
An Ethereum ERC-721 stores ownership as a mapping inside a shared smart contract, while a Sui NFT is an independent object with its own unique ID that lives directly under its owner’s address. On Sui, transferring an NFT doesn’t require calling a contract function — the object simply changes owners at the protocol level.
Can I complete this sui nft tutorial on Testnet without spending real money?
Yes — this sui nft tutorial runs entirely on Testnet using free tokens from the Sui faucet, so there is no cost involved. Testnet SUI tokens have no real-world value, and you can request more whenever your balance runs low.
What is the Object Display Standard and do I need it?
The Object Display Standard is a template system that tells Sui wallets and explorers how to render your NFT — which field is the image, the name, the project link, and so on. You don’t need it for the NFT to function on-chain, but without it your token will appear as an unlabeled object in most wallets and marketplaces.
How does Sui’s NFT model compare to Chialisp on Chia Network?
Both models treat assets as independent, uniquely identified objects rather than entries in a shared ledger, which is the key conceptual bridge. Chia NFTs use a coin with a puzzle hash for ownership, while Sui NFTs use an object with a UID, but both chains enforce uniqueness at the protocol level and give owners direct custody without requiring a contract call to verify ownership.
sui nft tutorial Citations
- Sui Documentation — NFTs Guide
- Sui Documentation — Object Display Standard
- Sui Documentation — Install Sui
- Sui Documentation — Soulbound NFT Example
- Sui Blog — All About NFT Standards
- Mysten Labs — Move AMA Recap (Todd Nowacki)
- PixelPlex — What Is the Sui Blockchain?
- CoinTelegraph Research — Sui’s Object-Centric Model and Move
- Sui Documentation — Ethereum to Sui Developer Guide
- ChiaTribe — Chialisp Transforming Blockchain: Unlocking Next-Gen Smart Contracts
