---
title: Metaplex Metadata
description:
  Learn how to add metadata to SPL Tokens using the Metaplex Token Metadata
  Program.
---

Standard SPL Tokens don't include metadata like a name, symbol, or image. The
[Metaplex Token Metadata Program](https://developers.metaplex.com/token-metadata)
solves this by creating a metadata account linked to each token mint.

<Callout type="info">
  This guide covers the Metaplex Token Metadata Program which can be used for
  standard SPL Tokens and Token-2022. To use the Token-2022 Metadata Extension,
  see the [Metadata Extension guide](/docs/tokens/extensions/metadata) which
  stores metadata directly on the mint account.
</Callout>

## How Token Metadata Works

The Token Metadata Program creates a Program Derived Address (PDA) for each
token mint. This metadata account stores onchain information like the token's
name and symbol, plus a URI pointing to off-chain JSON metadata (images,
descriptions, etc.).

```text
┌─────────────────┐       ┌─────────────────────┐
│   Mint Account  │       │  Metadata Account   │
│                 │       │  (PDA)              │
│  - Supply       │◄──────│  - Name             │
│  - Decimals     │       │  - Symbol           │
│  - Authority    │       │  - URI              │
└─────────────────┘       │  - Seller Fee       │
                          │  - Creators         │
                          └─────────────────────┘
```

The metadata PDA is derived from seeds: `["metadata", program_id, mint_address]`

## Create Token with Metadata

The `createV1` instruction creates both the mint account and its metadata in a
single transaction.

### Typescript

<CodeTabs storage="token-ts">

```ts !! title="Kit"
import {
  airdropFactory,
  appendTransactionMessageInstructions,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  generateKeyPairSigner,
  lamports,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners
} from "@solana/kit";
import {
  getCreateV1InstructionAsync,
  TokenStandard
} from "@metaplex-foundation/mpl-token-metadata-kit";

// Create connection
const rpc = createSolanaRpc("http://127.0.0.1:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://127.0.0.1:8900");

// Generate keypairs
const payer = await generateKeyPairSigner();
const mint = await generateKeyPairSigner();

// Fund payer
await airdropFactory({ rpc, rpcSubscriptions })({
  recipientAddress: payer.address,
  lamports: lamports(1_000_000_000n),
  commitment: "confirmed"
});

// Create fungible token with metadata
const createInstruction = await getCreateV1InstructionAsync({
  mint,
  authority: payer,
  payer,
  name: "My Token",
  symbol: "MTK",
  uri: "https://example.com/token.json",
  sellerFeeBasisPoints: 0,
  tokenStandard: TokenStandard.Fungible
});

// Build and send transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayerSigner(payer, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => appendTransactionMessageInstructions([createInstruction], tx)
);

const signedTransaction =
  await signTransactionMessageWithSigners(transactionMessage);

await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
  signedTransaction,
  { commitment: "confirmed" }
);

console.log("Mint Address:", mint.address);
```

```ts !! title="Kit Helper"
import {
  airdropFactory,
  appendTransactionMessageInstructions,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  generateKeyPairSigner,
  lamports,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners
} from "@solana/kit";
import { createFungible } from "@metaplex-foundation/mpl-token-metadata-kit";

// Create connection
const rpc = createSolanaRpc("http://127.0.0.1:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://127.0.0.1:8900");

// Generate keypairs
const payer = await generateKeyPairSigner();
const mint = await generateKeyPairSigner();

// Fund payer
await airdropFactory({ rpc, rpcSubscriptions })({
  recipientAddress: payer.address,
  lamports: lamports(1_000_000_000n),
  commitment: "confirmed"
});

// Use the createFungible helper for simpler API
const createInstruction = await createFungible({
  mint,
  authority: payer,
  payer,
  name: "My Token",
  symbol: "MTK",
  uri: "https://example.com/token.json",
  sellerFeeBasisPoints: 0
});

// Build and send transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayerSigner(payer, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => appendTransactionMessageInstructions([createInstruction], tx)
);

const signedTransaction =
  await signTransactionMessageWithSigners(transactionMessage);

await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
  signedTransaction,
  { commitment: "confirmed" }
);

console.log("Mint Address:", mint.address);
```

</CodeTabs>

## Fetch Token Metadata

To learn how to fetch metadata for existing tokens, see the
[Fetch Token Metadata](/developers/cookbook/tokens/fetch-token-metadata)
cookbook recipe.

## Update Token Metadata

The update authority can modify the metadata if the account is mutable.

<CodeTabs storage="token-ts">

```ts !! title="Kit"
import {
  appendTransactionMessageInstructions,
  createSolanaRpc,
  createSolanaRpcSubscriptions,
  createTransactionMessage,
  pipe,
  sendAndConfirmTransactionFactory,
  setTransactionMessageFeePayerSigner,
  setTransactionMessageLifetimeUsingBlockhash,
  signTransactionMessageWithSigners
} from "@solana/kit";
import {
  getUpdateV1InstructionAsync,
  findMetadataPda,
  fetchMetadata
} from "@metaplex-foundation/mpl-token-metadata-kit";

const rpc = createSolanaRpc("http://127.0.0.1:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://127.0.0.1:8900");

// authority must be a KeyPairSigner with update authority
const mintAddress = "YOUR_MINT_ADDRESS";

// Fetch current metadata to preserve existing values
const [metadataAddress] = await findMetadataPda({ mint: mintAddress });
const currentMetadata = await fetchMetadata(rpc, metadataAddress);

// Update metadata (must provide all data fields)
const updateInstruction = await getUpdateV1InstructionAsync({
  mint: mintAddress,
  authority, // Update authority signer
  payer: authority,
  data: {
    name: "Updated Token Name",
    symbol: "UPD",
    uri: "https://example.com/updated-token.json",
    sellerFeeBasisPoints: 100, // 1%
    creators:
      currentMetadata.data.creators.__option === "Some"
        ? currentMetadata.data.creators.value
        : null
  }
});

// Build and send transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

const transactionMessage = pipe(
  createTransactionMessage({ version: 0 }),
  (tx) => setTransactionMessageFeePayerSigner(authority, tx),
  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
  (tx) => appendTransactionMessageInstructions([updateInstruction], tx)
);

const signedTransaction =
  await signTransactionMessageWithSigners(transactionMessage);

await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
  signedTransaction,
  { commitment: "confirmed" }
);

console.log("Metadata updated successfully");
```

</CodeTabs>

## Token Standards

The Token Metadata Program supports different token standards:

| Standard                         | Description                                | Use Case            |
| -------------------------------- | ------------------------------------------ | ------------------- |
| `Fungible`                       | Standard fungible token with metadata      | Currencies, points  |
| `FungibleAsset`                  | Fungible token representing a unique asset | Semi-fungible items |
| `NonFungible`                    | NFT with Master Edition                    | 1/1 artwork         |
| `ProgrammableNonFungible`        | NFT with enforced royalties                | Creator royalties   |
| `NonFungibleEdition`             | Printed copy of an NFT                     | Limited editions    |
| `ProgrammableNonFungibleEdition` | Printed copy with enforced royalties       | Limited editions    |

```ts
import { TokenStandard } from "@metaplex-foundation/mpl-token-metadata-kit";

// For fungible tokens
tokenStandard: TokenStandard.Fungible;

// For NFTs
tokenStandard: TokenStandard.NonFungible;

// For programmable NFTs (enforced royalties)
tokenStandard: TokenStandard.ProgrammableNonFungible;
```

## Off-Chain Metadata Format

The `uri` field points to a JSON file containing extended metadata. The standard
format follows the
[Metaplex Token Metadata Standard](https://developers.metaplex.com/token-metadata/token-standard):

```json title="token-metadata.json"
{
  "name": "My Token",
  "symbol": "MTK",
  "description": "A description of the token",
  "image": "https://example.com/token-image.png",
  "external_url": "https://example.com",
  "attributes": [
    {
      "trait_type": "Category",
      "value": "Utility"
    }
  ],
  "properties": {
    "files": [
      {
        "uri": "https://example.com/token-image.png",
        "type": "image/png"
      }
    ]
  }
}
```

<Callout type="caution">
  Store your metadata JSON on a reliable, permanent storage solution like
  Arweave, IPFS, or a dedicated CDN. If the URI becomes inaccessible, wallets
  and explorers won't be able to display your token's metadata.
</Callout>

## Metadata Account Structure

The onchain metadata account contains:

```rust
pub struct Metadata {
    pub key: Key,                              // Account type identifier
    pub update_authority: Pubkey,              // Can update metadata
    pub mint: Pubkey,                          // Associated mint
    pub name: String,                          // Token name (max 32 chars)
    pub symbol: String,                        // Token symbol (max 10 chars)
    pub uri: String,                           // URI to off-chain JSON (max 200 chars)
    pub seller_fee_basis_points: u16,          // Royalty % (100 = 1%)
    pub creators: Option<Vec<Creator>>,        // Creator list with shares
    pub primary_sale_happened: bool,           // Primary sale flag
    pub is_mutable: bool,                      // Can metadata be updated
    pub edition_nonce: Option<u8>,             // Edition nonce
    pub token_standard: Option<TokenStandard>, // Token type
    pub collection: Option<Collection>,        // Collection info
    pub uses: Option<Uses>,                    // Use tracking
}
```

## Best Practices

1. **Set appropriate mutability**: Use `isMutable: false` for tokens that should
   never change
2. **Use reliable URI hosting**: Off-chain metadata should be on permanent
   storage
3. **Verify creators**: Creator addresses should be verified to confirm
   authenticity
4. **Consider royalties**: Set `sellerFeeBasisPoints` for secondary sale
   royalties (marketplaces may or may not enforce these)
