Monad chain is now live on Chainstack! Get reliable Monad Mainnet and Testnet RPC endpoints.    Learn more
  • Pricing
  • Docs

Token metadata on Solana: from convention to data

Created Jan 15, 2026 Updated Jan 15, 2026

In the previous post, we examined how the SPL Token program models fungible assets on Solana using accounts, authorities, and runtime enforcement.

One important question was left intentionally unanswered:

Where does token metadata live, and how does the runtime know how to find it?

On Solana, accounts do not carry intrinsic meaning. They are byte arrays interpreted by programs. Any relationship between accounts must be explicitly encoded, derived, or conventionally agreed upon.

Token metadata is a perfect example of this design tension.

Early Solana token designs relied on well-known conventions such as Program-Derived Addresses, to associate metadata with a mint. This approach worked, but it also introduced implicit assumptions that the runtime itself does not enforce.

In this post, we’ll examine how token metadata on Solana is modeled, why it was originally externalized into separate accounts, and why newer designs are moving toward explicit, data-driven references instead of convention.

The goal is not to describe a specific framework, but to understand the architectural trade-offs behind where token meaning lives on-chain.

Accounts do not carry meaning

At the runtime level, an account on Solana is nothing more than a byte array with an owner, lamports, and a data length. The runtime does not interpret that data, validate its structure, or attach any semantic meaning to it.

There is no native concept of:

  • a token
  • a balance
  • metadata
  • ownership semantics

All of that meaning is introduced entirely by programs.

This distinction is easy to miss because most developers interact with Solana through SDKs and frameworks that present a much richer mental model. A “Mint account” feels like a first-class object. A “Token Account” feels like a balance record. But those are program-level interpretations, not runtime primitives.

From the runtime’s perspective:

  • an SPL Token mint account is just an account owned by the SPL Token Program
  • a token account is just another account owned by the same program
  • the relationship between them is not enforced globally

The runtime does not know that a token account “belongs” to a mint.
It only enforces that the owning program is the one allowed to read and mutate the data.

This is a crucial property of Solana’s design:

Meaning is not intrinsic. It is contextual.

Every invariant you rely on: total supply, balances, authorities, decimals, exists only because a specific program chooses to enforce it when invoked.

The same principle applies to metadata.

There is nothing in the runtime that says:

  • “this account contains metadata”
  • “this metadata describes that mint”
  • “this metadata must exist”

Those are assumptions layered on top of the account model.

Why token metadata started outside the mint

Given this model, the natural question is: Why wasn’t metadata stored directly inside the mint account from the beginning? The answer is not ideological, it is structural.

Early SPL Token mint accounts had:

  • a fixed layout
  • a fixed size
  • no mechanism for optional or extensible fields

Once a mint was created, its data layout was effectively frozen.
There was no safe way to append arbitrary information without breaking compatibility.

At the same time, metadata had very different requirements from the core token state:

  • variable-length fields: names, symbols and URIs
  • evolving schemas
  • different authority rules
  • different lifecycle expectations

Putting metadata into the mint would have tightly coupled all of that complexity to the token program itself. Instead, Solana developers leaned on one of the runtime’s strongest primitives: Program-Derived Addresses (PDAs).

A metadata account could be:

  • deterministically derived from the mint address
  • owned by a separate program
  • versioned independently
  • optional

This led to the now-familiar pattern: The mint account contains only core token state, lives in a separate program-owned account and the relationship between them is defined by a derivation rule, Crucially, this relationship is not enforced by the runtime. All of those guarantees exist only because clients and programs agree to look in the same place.

Metadata as data, introduction to Metaplex

Once metadata was separated from the mint, the ecosystem needed more than an informal pattern. It needed a shared interpretation layer something that could describe what metadata looks like, who can update it, and how clients should reason about it.

This is the role played by Metaplex. Metaplex does not change how tokens work at the runtime level. It does not introduce a new asset type, nor does it modify the SPL Token program. Instead, it introduces a dedicated program whose only responsibility is to interpret metadata stored in accounts.

That program, the Metaplex Token Metadata Program, formalizes a simple but powerful idea: a token’s human-meaningful description should live in a separate account, owned and governed independently from the token’s supply and balances.

The relationship between a mint and its metadata is established through a deterministic derivation rule. Given a mint address, the metadata account can be derived as a Program-Derived Address using a fixed seed and the metadata program ID. This makes the relationship predictable without embedding any metadata logic into the token program itself.

The metadata account contains structured fields such as the token’s name, symbol, URI, and update authority. From the token program’s point of view, none of this exists. From Metaplex’s point of view, this is the authoritative description of what the token represents.

Note: Metaplex’s seeds can be found here

Example

Lets mint a new token and deploy the Metadata for this token, this example assumes you read the previous blog post and understand whats going on,

Token minting:

spl-token create-token --decimals 9

Token account creation:

spl-token create-account H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D

Minting tokens to the created account:

spl-token mint H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D 1000

Metadata creation:

import { createMetadataAccountV3 } from "@metaplex-foundation/mpl-token-metadata";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { keypairIdentity, createSignerFromKeypair } from "@metaplex-foundation/umi";
import { publicKey } from "@metaplex-foundation/umi-public-keys";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
const keypairPath = path.join(os.homedir(), ".config", "solana", "id.json");
const secretKey = JSON.parse(fs.readFileSync(keypairPath, "utf8"));
const mint = "H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D";
async function main() {
  // Create UMI instance
  const umi = createUmi("https://api.devnet.solana.com");
  // Set up the keypair identity
  const keypair = umi.eddsa.createKeypairFromSecretKey(
    new Uint8Array(secretKey)
  );
  umi.use(keypairIdentity(keypair));
  // Create signer from keypair
  const mintAuthoritySigner = createSignerFromKeypair(umi, keypair);
  // Create metadata account
  const tx = createMetadataAccountV3(umi, {
    mint: publicKey(mint),
    mintAuthority: mintAuthoritySigner,
    data: {
      name: "Example Token",
      symbol: "EXMPL",
      uri: "https://example.com/metadata.json",
      sellerFeeBasisPoints: 0,
      creators: null,
      collection: null,
      uses: null,
    },
    isMutable: true,
    collectionDetails: null,
  });
  const signature = await tx.sendAndConfirm(umi);
  console.log("Transaction signature:", signature);
}
main().catch((error) => {
  console.error("Error:", error);
  process.exit(1);
});

This code created this account:

https://solscan.io/tx/
5Zu5v2Hicwi4UuutHK6XEah6hc1FXRvSG71zdhoqdq2BotjBvonPMBfEp1TK8CAyfn85G6kRdx4viJrLe32PuB3B?cluster=devnet

To read the Metadata of the token we can use this code:

import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { publicKey } from "@metaplex-foundation/umi-public-keys";
import { fetchDigitalAsset } from "@metaplex-foundation/mpl-token-metadata";
const MINT_ADDRESS = "H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D";
const RPC_URL = "https://api.devnet.solana.com";
async function main() {
  console.log("Checking metadata for mint:", MINT_ADDRESS);
  // Create UMI instance
  const umi = createUmi(RPC_URL);
  try {
    // Fetch the digital asset (mint + metadata + edition info)
    const mint = publicKey(MINT_ADDRESS);
    const digitalAsset = await fetchDigitalAsset(umi, mint);
    const metadata = digitalAsset.metadata;
    console.log("\nMetadata found!");
    console.log("\nToken Metadata:");
    console.log("─".repeat(50));
    console.log("Name:                ", metadata.name);
    console.log("Symbol:              ", metadata.symbol);
    console.log("URI:                 ", metadata.uri);
    console.log("Seller Fee (bps):    ", metadata.sellerFeeBasisPoints);
    console.log("Is Mutable:          ", metadata.isMutable);
    console.log("Update Authority:    ", metadata.updateAuthority);
    console.log("\n Mint Info:");
    console.log("Mint Address:        ", digitalAsset.mint.publicKey);
    console.log("Decimals:            ", digitalAsset.mint.decimals);
    console.log("Supply:              ", digitalAsset.mint.supply);
    console.log("Mint Authority:      ", digitalAsset.mint.mintAuthority);
    console.log("Freeze Authority:    ", digitalAsset.mint.freezeAuthority);
  } catch (error) {
    if (error.message?.includes("Account Not Found")) {
      console.log("\n No metadata found for this token mint");
      console.log("The token exists but doesn't have metadata associated with it yet");
      console.log("Use metadata.ts to create metadata for this token");
    } else {
      throw error;
    }
  }
}
main().catch((error) => {
  console.error("Error:", error);
  process.exit(1);
});

Output:

The hidden cost of convention

The Metaplex model worked and still works because it created a shared convention across the ecosystem. Wallets, marketplaces, indexers, and SDKs all agree on where metadata lives and how to interpret it. But that agreement exists outside the protocol.

The Solana runtime does not enforce the relationship between a mint and its metadata. A mint does not contain a reference to its metadata account. The metadata account does not declare which mint it describes. The link exists only because every participant in the ecosystem already knows how to derive it.

This means correctness depends on shared knowledge rather than explicit state.

If metadata is missing, nothing fails at the protocol level. If metadata is malformed, the runtime does not object. If a token chooses to store metadata somewhere else, the chain does not prevent it. These are all valid states from Solana’s point of view.

The cost of this design is not immediately visible in happy paths. It appears at the boundaries: when programs disagree, when tooling makes assumptions, or when new use cases stretch old conventions.

At that point, the chain cannot help you. There is no on-chain way to verify that “this account is the metadata for that mint” unless every program involved already agrees on the same rules.

Summary

Token metadata on Solana has always existed outside the runtime’s understanding of token relationships. The difference between early designs is not what metadata contains, but how its location and association are communicated.

Convention-based designs rely on shared assumptions between programs and tooling. The Metaplex Token Metadata program standardized those assumptions, but did not make them part of the runtime’s enforcement.

This creates a clear boundary between what the protocol enforces and what the ecosystem agrees upon.

In the next post, we’ll examine how newer token program designs move beyond convention, and what it means to make token relationships explicit rather than assumed.

Learn more about Solana architecture from our articles

Reliable Solana RPC infrastructure

Getting started with Solana on Chainstack is fast and straightforward. Developers can deploy a reliable Solana node within seconds through an intuitive Console — no complex setup or hardware management required. 

Chainstack provides low-latency Solana RPC access and real-time gRPC data streaming via Yellowstone Geyser Plugin, ensuring seamless connectivity for building, testing, and scaling DeFi, analytics, and trading applications. With Solana low-latency endpoints powered by global infrastructure, you can achieve lightning-fast response times and consistent performance across regions. 

Start for free, connect your app to a reliable Solana RPC endpoint, and experience how easy it is to build and scale on Solana with Chainstack – one of the best RPC providers.

SHARE THIS ARTICLE

Andrey Obruchkov

Senior Software Engineer and blockchain specialist with hands-on experience building across EVM, Solana, Bitcoin, Cosmos, Aptos, and Sui ecosystems from smart contracts and DeFi infrastructure to cross-chain integrations and developer tooling.

Customer Stories

Aleph One

Operate smarter and more efficiently with seamless integration of decentralized applications.

Defined

Defined deliver real-time blockchain data for over 2M tokens and 800M NFTs with reliable Web3 infrastructure.

Trava.Finance

Reliable and high-performance infrastructure across multiple blockchain networks.