DEV Community

Cover image for Solana NFTs Without Metaplex: A Week of Token Extensions
Vinay
Vinay

Posted on

Solana NFTs Without Metaplex: A Week of Token Extensions

An NFT on Solana is just a mint with supply 1, decimals 0, and metadata.

Before this week I thought a Solana NFT required Metaplex. Every tutorial pointed at the Metaplex Token Metadata program as the standard way to add a name, image, and collection to a token. It turns out you can mint a full NFT — with on-chain metadata, a verifiable collection membership, and live-updatable fields — using nothing but the Token Extensions program and a handful of CLI commands. No custom smart contract. No framework. Just the protocol.

Here is what I built across four days and what surprised me.

Day 1 — An NFT with on-chain metadata

The mental model first. An NFT on Solana is a token mint with three properties: supply of 1, zero decimals (so you cannot split it), and metadata that wallets can read. With Token Extensions, the metadata lives directly on the mint account itself — no separate Metaplex account, no PDA derivation, no extra lookups.

I created a vanity mint address using solana-keygen grind --starts-with nft:1, then minted the token with the metadata extension enabled:

spl-token create-token \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --enable-metadata \
  --decimals 0 \
  ./nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt.json
Enter fullscreen mode Exit fullscreen mode

Then I stamped it with a name, symbol, and a URI pointing to a JSON file on a GitHub Gist:

spl-token initialize-metadata nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt \
  "First Light" "LIGHT" \
  "https://gist.githubusercontent.com/bl4ck4t/e0899c5a417f0d40cf72fd48201ab11f/raw/metadata.json"

spl-token mint nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt 1
spl-token authorize nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt mint --disable
Enter fullscreen mode Exit fullscreen mode

The --enable-metadata flag reserves space for both the metadata pointer and the metadata fields at creation time. You cannot add this later — the account size is fixed when the mint is born.

NFT Image

Day 2 — Grouping NFTs into an on-chain collection

A collection on Solana is just another mint with a group extension. Individual NFTs link back to it with a member extension. Together they replace what would be a collections table joined to an nfts table in Web2.

Created a collection mint:

spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token \
  --decimals 0 --enable-metadata --enable-group

spl-token initialize-metadata 2MEg3Vqbaiy1gNRciiy8f33eqVrenLXj26NDjHPbr7kB \
  "Solana Sketchbook" "SKTCH" \
  "https://gist.githubusercontent.com/janvinsha/b477ebe4dda46b0ef03895c4ea930a46/raw/f29222bcaff0d4979fe7ebb610a00bb97a8418ec/collection.json"

spl-token initialize-group 2MEg3Vqbaiy1gNRciiy8f33eqVrenLXj26NDjHPbr7kB 3
Enter fullscreen mode Exit fullscreen mode

Then created two member NFTs that point back to it:

# Member #1
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token \
  --decimals 0 --enable-metadata --enable-member

spl-token initialize-metadata 9QiJaKzNRUuzw1jowpoQzYWW6KuEcgfZrzrpu7DKWMym \
  "Sketch #1" "SK1" \
  "https://gist.githubusercontent.com/janvinsha/3412c5d4e92b6de9a2ed82337ecafc44/raw/99359fc62ffd0480b6a52ee1ad4048ecba4ae61c/nft.json"
spl-token initialize-member 9QiJaKzNRUuzw1jowpoQzYWW6KuEcgfZrzrpu7DKWMym 2MEg3Vqbaiy1gNRciiy8f33eqVrenLXj26NDjHPbr7kB

# Member #2
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token \
  --decimals 0 --enable-metadata --enable-member

spl-token initialize-metadata 32KYgijQ8cQmAko7PDBo63BpCG4nby8XLksGKrehXSsy \
  "Sketch #2" "SK2" \
  "https://gist.githubusercontent.com/janvinsha/3412c5d4e92b6de9a2ed82337ecafc44/raw/99359fc62ffd0480b6a52ee1ad4048ecba4ae61c/nft.json"
spl-token initialize-member 32KYgijQ8cQmAko7PDBo63BpCG4nby8XLksGKrehXSsy 2MEg3Vqbaiy1gNRciiy8f33eqVrenLXj26NDjHPbr7kB
Enter fullscreen mode Exit fullscreen mode

The initialize-member command writes the collection address into the member mint's extension data. Wallets and explorers walk that pointer the same way a SQL query follows a foreign key.

Day 3 — Auditing everything on chain

After building, I read everything back using spl-token display:

spl-token display nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt
spl-token display 2MEg3Vqbaiy1gNRciiy8f33eqVrenLXj26NDjHPbr7kB
Enter fullscreen mode Exit fullscreen mode

The output shows every extension as a separate block — metadata, group, member — all parsed from raw account bytes. No off-chain index required.

I also confirmed the parent reference. The Group address field on each member mint matched the collection mint address byte-for-byte. That is what "verifiable provenance" means — two byte arrays comparing equal.

Day 4 — Mutating metadata live on devnet

Since I held the update authority, I renamed the NFT, added a custom field, then removed it:

spl-token update-metadata nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt name "Field Notes"
spl-token update-metadata nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt rarity legendary
spl-token update-metadata nftChe7XoDvsFDkQmavkhQhPv1T9n3pfxKm2A48E9dt rarity --remove
Enter fullscreen mode Exit fullscreen mode

Mutated metadata

Each command is a single transaction. The name and URI move instantly on-chain. The image those bytes point at (off-chain) is cached aggressively by wallets — a good lesson in the asymmetry between the two layers.

What surprised me

Extensions are permanent. Every extension you want must be declared at mint creation. There is no ALTER TABLE on Solana. If you forget --enable-metadata, you start over with a new mint. This feels restrictive coming from Web2, but it guarantees that any wallet can trust the account layout without additional verification.

Rent scales with extensions. A simple frozen mint is 171 bytes (~0.002 SOL rent). My multi-extension mint with metadata + group + member was 599 bytes (~0.005 SOL). Features cost bytes, and bytes cost SOL.

The update authority is powerful. Whoever holds the metadata update authority can rename, re-URI, and rewrite any field at any time. On mainnet, this should be a multisig or a revoked key for immutable NFTs.

Where to go deeper

The official documentation covers every extension I used:

If you are coming from Web2 and learning Solana, start by minting a single NFT with metadata. Then add a collection. Then mutate something. Each step builds the mental model that an NFT is not magic — it is just a mint with supply 1, decimals 0, and the right extensions.

Top comments (0)