---
title: Metadata Pointer & Token Metadata
description:
  Create a mint that stores metadata directly on the mint account using the
  MetadataPointer and TokenMetadata extensions, then update, remove, and lock
  that metadata through the token-metadata interface.
url: /docs/tokens/extensions/metadata
type: tutorial
prerequisites:
  - /docs/tokens/basics/create-mint
related:
  - /docs/tokens/metaplex
  - /docs/tokens/basics/mint-tokens
  - /docs/tokens/extensions
---

## What Are Metadata Pointer and Token Metadata Extensions?

The Token Extension Program's _rs`MetadataPointer`_ mint extension stores two
fields on a mint:

- An authority that can update the pointer
- The address of the account that stores the token's metadata

That pointer can reference any account owned by a program implementing the
[token-metadata interface](https://github.com/solana-program/token-metadata/tree/main/interface).

The Token Extension Program also implements that same interface directly through
the _rs`TokenMetadata`_ mint extension. With _rs`TokenMetadata`_, the mint
stores the token's _rs`name`_, _rs`symbol`_, _rs`uri`_, _rs`update_authority`_,
and custom metadata on the mint account itself.

<Callout type="info" title="Off-chain Metadata URI">
  The _rs`uri`_ field points to off-chain JSON metadata. See [Off-chain Metadata
  Format](/docs/tokens/metaplex#off-chain-metadata-format).
</Callout>

The two extensions solve different problems:

- _rs`MetadataPointer`_ specifies the account that stores metadata.
- _rs`TokenMetadata`_ stores the metadata directly on the mint account.

<Callout type="info" title="Variable Length Extension">
  _rs`TokenMetadata`_ is a variable length TLV extension.
  _rs`InitializeTokenMetadata`_, _rs`UpdateField`_, and _rs`RemoveKey`_ can
  resize the mint account data, but they do not transfer additional lamports.
  The mint account needs enough lamports to remain rent-exempt for the metadata
  being stored. If the metadata grows later, additional lamports need to be
  transferred to the mint account before the instruction that resizes it.
</Callout>

## How to Store Token Metadata on the Mint Account

To store metadata on the mint account:

1. Calculate the mint account size and rent needed for the mint, extensions, and
   metadata.
2. Create the mint account with _rs`CreateAccount`_, initialize
   _rs`MetadataPointer`_, and initialize the mint with _rs`InitializeMint`_.
3. Initialize _rs`TokenMetadata`_ on the mint account, then use
   _rs`UpdateField`_ to add or update metadata.
4. Use _rs`RemoveKey`_, _rs`UpdateAuthority`_, and _rs`Emit`_ to remove custom
   metadata, change or clear the update authority, or return the current
   metadata in transaction return data.

<ScrollyCoding>

## !!steps Calculate account size

Calculate the mint account size for the base mint plus the _rs`MetadataPointer`_
extension. This is the size used in _rs`CreateAccount`_.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

// !focus(1:4)
const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

// !focus(1:1)
const mintSpace = BigInt(getMintSize([metadataPointerExtension]));
```

## !!steps Calculate rent

Calculate rent using the maximum size needed after _rs`TokenMetadata`_ is stored
on the mint.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

// !focus(1:16)
const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();
```

## !!steps Create the mint account

Create the mint account with the calculated space and lamports.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();

// !focus(1:10)
await client.sendTransaction([
  getCreateAccountInstruction({
    payer: client.payer,
    newAccount: mint,
    lamports: mintRent,
    space: mintSpace,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  })
]);
```

## !!steps Initialize MetadataPointer

Initialize _rs`MetadataPointer`_ and set its metadata address to the mint
address.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();

await client.sendTransaction([
  getCreateAccountInstruction({
    payer: client.payer,
    newAccount: mint,
    lamports: mintRent,
    space: mintSpace,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  }),
  // !focus(1:5)
  getInitializeMetadataPointerInstruction({
    mint: mint.address,
    authority: client.payer.address,
    metadataAddress: mint.address
  })
]);
```

## !!steps Initialize the mint

Initialize the mint with _rs`InitializeMint`_ in the same transaction.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();

await client.sendTransaction([
  getCreateAccountInstruction({
    payer: client.payer,
    newAccount: mint,
    lamports: mintRent,
    space: mintSpace,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  }),
  getInitializeMetadataPointerInstruction({
    mint: mint.address,
    authority: client.payer.address,
    metadataAddress: mint.address
  }),
  // !focus(1:6)
  getInitializeMintInstruction({
    mint: mint.address,
    decimals: 0,
    mintAuthority: client.payer.address,
    freezeAuthority: client.payer.address
  })
]);
```

## !!steps Initialize and update metadata

Initialize _rs`TokenMetadata`_ on the mint. Use _rs`UpdateField`_ to add custom
metadata; if the field does not exist, _rs`UpdateField`_ adds it.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();

await client.sendTransaction([
  getCreateAccountInstruction({
    payer: client.payer,
    newAccount: mint,
    lamports: mintRent,
    space: mintSpace,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  }),
  getInitializeMetadataPointerInstruction({
    mint: mint.address,
    authority: client.payer.address,
    metadataAddress: mint.address
  }),
  getInitializeMintInstruction({
    mint: mint.address,
    decimals: 0,
    mintAuthority: client.payer.address,
    freezeAuthority: client.payer.address
  }),
  // !focus(1:16)
  getInitializeTokenMetadataInstruction({
    metadata: mint.address,
    updateAuthority: client.payer.address,
    mint: mint.address,
    mintAuthority: client.payer,
    name: "Example Token",
    symbol: "EXMPL",
    uri: "https://example.com/token.json"
  }),
  getUpdateTokenMetadataFieldInstruction({
    metadata: mint.address,
    updateAuthority: client.payer,
    field: tokenMetadataField("Key", ["description"]),
    value: "Metadata stored on mint account"
  })
]);
```

## !!steps Update, remove, or emit metadata

After the mint is initialized, use _rs`UpdateField`_ to update metadata,
_rs`UpdateMetadataPointer`_ to update the metadata pointer, _rs`RemoveKey`_ to
remove custom metadata, _rs`UpdateAuthority`_ to clear the update authority, and
_rs`Emit`_ to return the current metadata.

<CodePlaceholder title="Example" />

```ts !! title="Example"
const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();

await client.sendTransaction([
  getCreateAccountInstruction({
    payer: client.payer, // Account funding account creation.
    newAccount: mint, // New mint account to create.
    lamports: mintRent, // Lamports funding the mint account rent.
    space: mintSpace, // Account size in bytes for the mint plus MetadataPointer.
    programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.
  }),
  getInitializeMetadataPointerInstruction({
    mint: mint.address, // Mint account that stores the MetadataPointer extension.
    authority: client.payer.address, // Authority allowed to update the metadata pointer later.
    metadataAddress: mint.address // Account address that stores the metadata.
  }),
  getInitializeMintInstruction({
    mint: mint.address, // Mint account to initialize.
    decimals: 0, // Number of decimals for the token.
    mintAuthority: client.payer.address, // Authority allowed to mint new tokens.
    freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.
  }),
  getInitializeTokenMetadataInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer.address, // Authority allowed to update metadata later.
    mint: mint.address, // Mint that the metadata describes.
    mintAuthority: client.payer, // Signer authorizing metadata initialization for the mint.
    name: "Example Token", // Token name stored in metadata.
    symbol: "EXMPL", // Token symbol stored in metadata.
    uri: "https://example.com/token.json" // URI pointing to off-chain JSON metadata.
  }),
  getUpdateTokenMetadataFieldInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer, // Signer authorized to update metadata fields.
    field: tokenMetadataField("Key", ["description"]), // Custom metadata field to add.
    value: "Metadata stored on mint account" // Value stored for the custom metadata field.
  })
]);

// !focus(1:31)
await client.sendTransaction([
  getUpdateTokenMetadataFieldInstruction({
    metadata: mint.address,
    updateAuthority: client.payer,
    field: tokenMetadataField("Name"),
    value: "Example Token v2"
  }),
  getUpdateMetadataPointerInstruction({
    mint: mint.address,
    metadataPointerAuthority: client.payer,
    metadataAddress: mint.address
  }),
  getRemoveTokenMetadataKeyInstruction({
    metadata: mint.address,
    updateAuthority: client.payer,
    key: "description"
  }),
  getUpdateTokenMetadataUpdateAuthorityInstruction({
    metadata: mint.address,
    updateAuthority: client.payer,
    newUpdateAuthority: null
  }),
  getEmitTokenMetadataInstruction({
    metadata: mint.address
  })
]);
```

</ScrollyCoding>

<Callout type="info" title="Instruction Order">
  _rs`MetadataPointerInstruction::Initialize`_ must come before
  _rs`InitializeMint`_. _rs`CreateAccount`_,
  _rs`MetadataPointerInstruction::Initialize`_, and _rs`InitializeMint`_ must be
  included in the same transaction.
</Callout>

### Source Reference

#### Metadata Pointer

| Item                                         | Description                                                                                                       | Source                                                                                                                                        |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| _rs`MetadataPointer`_                        | Mint extension that stores the metadata pointer authority and the metadata account address.                       | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/interface/src/extension/metadata_pointer/mod.rs#L12-L22)         |
| _rs`MetadataPointerInstruction::Initialize`_ | Initializes the metadata pointer extension before _rs`InitializeMint`_.                                           | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/interface/src/extension/metadata_pointer/instruction.rs#L22-L38) |
| _rs`MetadataPointerInstruction::Update`_     | Updates the metadata address stored by the mint's metadata pointer extension.                                     | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/interface/src/extension/metadata_pointer/instruction.rs#L39-L55) |
| _rs`process_initialize`_ (`MetadataPointer`) | Metadata pointer processor logic that requires at least an authority or a metadata address during initialization. | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/metadata_pointer/processor.rs#L25-L48)     |
| _rs`process_update`_ (`MetadataPointer`)     | Validates the metadata pointer authority, then rewrites the mint's stored metadata address.                       | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/metadata_pointer/processor.rs#L51-L77)     |

#### Token Metadata

| Item                                            | Description                                                                                                                         | Source                                                                                                                                    |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| _rs`TokenMetadata`_                             | Variable-length token-metadata interface state stored in a TLV entry.                                                               | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/state.rs#L18-L38)                         |
| _rs`Field`_                                     | Field enum used by _rs`UpdateField`_ to target _rs`name`_, _rs`symbol`_, _rs`uri`_, or a custom key.                                | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/state.rs#L109-L122)                       |
| _rs`TokenMetadataInstruction::Initialize`_      | Initializes the base _rs`name`_, _rs`symbol`_, and _rs`uri`_ fields for a token-metadata account.                                   | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/instruction.rs#L81-L95)                   |
| _rs`TokenMetadataInstruction::UpdateField`_     | Adds or updates a base field or custom metadata field on the token metadata.                                                        | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/instruction.rs#L97-L117)                  |
| _rs`TokenMetadataInstruction::RemoveKey`_       | Removes a custom metadata key. Base fields cannot be removed with this instruction.                                                 | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/instruction.rs#L119-L134)                 |
| _rs`TokenMetadataInstruction::UpdateAuthority`_ | Rotates the metadata update authority, or clears it entirely to make metadata immutable.                                            | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/instruction.rs#L136-L144)                 |
| _rs`TokenMetadataInstruction::Emit`_            | Returns the serialized metadata through transaction return data, optionally for a byte range.                                       | [Source](https://github.com/solana-program/token-metadata/blob/interface%40v1.0.0/interface/src/instruction.rs#L146-L159)                 |
| _rs`process_initialize`_ (`TokenMetadata`)      | Token-metadata processor logic that requires the metadata account to be the mint itself and the mint to have _rs`MetadataPointer`_. | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/token_metadata/processor.rs#L43-L101)  |
| _rs`process_update_field`_                      | Reallocates and rewrites the variable length _rs`TokenMetadata`_ TLV entry after a field update.                                    | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/token_metadata/processor.rs#L104-L132) |
| _rs`process_remove_key`_                        | Validates the update authority, removes a custom metadata key, and rewrites the TLV entry.                                          | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/token_metadata/processor.rs#L135-L159) |
| _rs`process_update_authority`_                  | Validates the current update authority, then rotates or clears the metadata authority in place.                                     | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/token_metadata/processor.rs#L162-L187) |
| _rs`process_emit`_                              | Reads the serialized _rs`TokenMetadata`_ from the mint and writes the requested slice to return data.                               | [Source](https://github.com/solana-program/token-2022/blob/program%40v10.0.0/program/src/extension/token_metadata/processor.rs#L190-L203) |

### Typescript

The `Kit` example below uses the generated instructions directly. Legacy
examples using `@solana/web3.js`, `@solana/spl-token`, and
`@solana/spl-token-metadata` are included for reference.

#### Kit

<CodeTabs storage="token-ts-kit" flags="r">

```ts !! title="Instructions"
import {
  lamports,
  createClient,
  generateKeyPairSigner,
  unwrapOption
} from "@solana/kit";
import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";
import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";
import { unpack as unpackTokenMetadata } from "@solana/spl-token-metadata";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
  extension,
  fetchMint,
  getEmitTokenMetadataInstruction,
  getInitializeMetadataPointerInstruction,
  getInitializeMintInstruction,
  getInitializeTokenMetadataInstruction,
  getMintSize,
  getRemoveTokenMetadataKeyInstruction,
  getUpdateMetadataPointerInstruction,
  getUpdateTokenMetadataFieldInstruction,
  getUpdateTokenMetadataUpdateAuthorityInstruction,
  isExtension,
  tokenMetadataField,
  TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";

const client = await createClient()
  .use(generatedPayer())
  .use(
    solanaRpc({
      rpcUrl: "http://localhost:8899",
      rpcSubscriptionsUrl: "ws://localhost:8900"
    })
  )
  .use(rpcAirdrop())
  .use(airdropPayer(lamports(1_000_000_000n)));

const mint = await generateKeyPairSigner();

const metadataPointerExtension = extension("MetadataPointer", {
  authority: client.payer.address,
  metadataAddress: mint.address
});

const mintSpace = BigInt(getMintSize([metadataPointerExtension]));

const maxTokenMetadataExtension = extension("TokenMetadata", {
  updateAuthority: client.payer.address,
  mint: mint.address,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: new Map([
    ["description", "Metadata stored on mint account"]
  ])
});
const maxMintSpace = BigInt(
  getMintSize([metadataPointerExtension, maxTokenMetadataExtension])
);
const mintRent = await client.rpc
  .getMinimumBalanceForRentExemption(maxMintSpace)
  .send();

await client.sendTransaction([
  // !mark(1:7)
  getCreateAccountInstruction({
    payer: client.payer, // Account funding account creation.
    newAccount: mint, // New mint account to create.
    lamports: mintRent, // Lamports funding the mint account rent.
    space: mintSpace, // Account size in bytes for the mint plus MetadataPointer.
    programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.
  }),
  // !mark(1:5)
  getInitializeMetadataPointerInstruction({
    mint: mint.address, // Mint account that stores the MetadataPointer extension.
    authority: client.payer.address, // Authority allowed to update the metadata pointer later.
    metadataAddress: mint.address // Account address that stores the metadata.
  }),
  // !mark(1:6)
  getInitializeMintInstruction({
    mint: mint.address, // Mint account to initialize.
    decimals: 0, // Number of decimals for the token.
    mintAuthority: client.payer.address, // Authority allowed to mint new tokens.
    freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.
  }),
  // !mark(1:9)
  getInitializeTokenMetadataInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer.address, // Authority allowed to update metadata later.
    mint: mint.address, // Mint that the metadata describes.
    mintAuthority: client.payer, // Signer authorizing metadata initialization for the mint.
    name: "Example Token", // Token name stored in metadata.
    symbol: "EXMPL", // Token symbol stored in metadata.
    uri: "https://example.com/token.json" // URI pointing to off-chain JSON metadata.
  }),
  // !mark(1:6)
  getUpdateTokenMetadataFieldInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer, // Signer authorized to update metadata fields.
    field: tokenMetadataField("Key", ["description"]), // Custom metadata field to add.
    value: "Metadata stored on mint account" // Value stored for the custom metadata field.
  })
]);

const initialMintAccount = await fetchMint(client.rpc, mint.address);
const initialExtensions =
  unwrapOption(initialMintAccount.data.extensions) ?? [];
const initialTokenMetadata = initialExtensions.find((item) =>
  isExtension("TokenMetadata", item)
);

console.dir(
  {
    mint: mint.address,
    tokenMetadata: initialTokenMetadata
  },
  { depth: null }
);

const updateMetadataTransaction = await client.sendTransaction([
  // !mark(1:6)
  getUpdateTokenMetadataFieldInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer, // Signer authorized to update metadata fields.
    field: tokenMetadataField("Name"), // Base metadata field to update.
    value: "Example Token v2" // Updated value for the token name.
  }),
  // !mark(1:5)
  getUpdateMetadataPointerInstruction({
    mint: mint.address, // Mint account that stores the MetadataPointer extension.
    metadataPointerAuthority: client.payer, // Signer authorized to update the metadata pointer.
    metadataAddress: mint.address // Account address that stores the metadata.
  }),
  // !mark(1:5)
  getRemoveTokenMetadataKeyInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer, // Signer authorized to remove custom metadata.
    key: "description" // Custom metadata key to remove.
  }),
  // !mark(1:5)
  getUpdateTokenMetadataUpdateAuthorityInstruction({
    metadata: mint.address, // Mint account that stores the metadata.
    updateAuthority: client.payer, // Current signer authorized to change the update authority.
    newUpdateAuthority: null // Clear the update authority so metadata can no longer be changed.
  }),
  // !mark(1:3)
  getEmitTokenMetadataInstruction({
    metadata: mint.address // Mint account that stores the metadata to emit.
  })
]);

const updateMetadataResult = await client.rpc
  .getTransaction(updateMetadataTransaction.context.signature, {
    encoding: "json",
    maxSupportedTransactionVersion: 0
  })
  .send();
const emittedDataBase64 = updateMetadataResult?.meta?.returnData?.data?.[0];
if (!emittedDataBase64) {
  throw new Error("Expected token metadata return data");
}
const emittedTokenMetadata = unpackTokenMetadata(
  Buffer.from(emittedDataBase64, "base64")
);

const mintAccount = await fetchMint(client.rpc, mint.address);
const extensions = unwrapOption(mintAccount.data.extensions) ?? [];
const metadataPointer = extensions.find((item) =>
  isExtension("MetadataPointer", item)
);
const tokenMetadata = extensions.find((item) =>
  isExtension("TokenMetadata", item)
);

console.dir(
  {
    mint: mint.address,
    metadataPointer,
    tokenMetadata,
    emittedTokenMetadata
  },
  { depth: null }
);
```

</CodeTabs>

#### Web3.js

<CodeTabs storage="token-ts-legacy" flags="r">

```ts !! title="Instructions"
import {
  Connection,
  Keypair,
  LAMPORTS_PER_SOL,
  sendAndConfirmTransaction,
  SystemProgram,
  Transaction
} from "@solana/web3.js";
import {
  createInitializeMetadataPointerInstruction,
  createInitializeMintInstruction,
  createUpdateMetadataPointerInstruction,
  ExtensionType,
  getMetadataPointerState,
  getMint,
  getMintLen,
  getTokenMetadata,
  LENGTH_SIZE,
  TOKEN_2022_PROGRAM_ID,
  TYPE_SIZE
} from "@solana/spl-token";
import {
  createInitializeInstruction,
  createEmitInstruction,
  createRemoveKeyInstruction,
  unpack as unpackTokenMetadata,
  createUpdateAuthorityInstruction,
  createUpdateFieldInstruction,
  pack,
  type TokenMetadata
} from "@solana/spl-token-metadata";

const connection = new Connection("http://localhost:8899", "confirmed");
const latestBlockhash = await connection.getLatestBlockhash();

const feePayer = Keypair.generate();
const mint = Keypair.generate();

const airdropSignature = await connection.requestAirdrop(
  feePayer.publicKey,
  LAMPORTS_PER_SOL
);
await connection.confirmTransaction({
  blockhash: latestBlockhash.blockhash,
  lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
  signature: airdropSignature
});

const maxMetadata: TokenMetadata = {
  updateAuthority: feePayer.publicKey,
  mint: mint.publicKey,
  name: "Example Token v2",
  symbol: "EXMPL",
  uri: "https://example.com/token.json",
  additionalMetadata: [["description", "Metadata stored on mint account"]]
};

const mintSpace = getMintLen([ExtensionType.MetadataPointer]);
const metadataSpace = TYPE_SIZE + LENGTH_SIZE + pack(maxMetadata).length;
const mintRent = await connection.getMinimumBalanceForRentExemption(
  mintSpace + metadataSpace
);

await sendAndConfirmTransaction(
  connection,
  new Transaction().add(
    // !mark(1:7)
    SystemProgram.createAccount({
      fromPubkey: feePayer.publicKey, // Account funding account creation.
      newAccountPubkey: mint.publicKey, // New mint account to create.
      lamports: mintRent, // Lamports funding the mint account rent.
      space: mintSpace, // Account size in bytes for the mint plus MetadataPointer.
      programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
    }),
    // !mark(1:6)
    createInitializeMetadataPointerInstruction(
      mint.publicKey, // Mint account that stores the MetadataPointer extension.
      feePayer.publicKey, // Authority allowed to update the metadata pointer later.
      mint.publicKey, // Account address that stores the metadata.
      TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
    ),
    // !mark(1:7)
    createInitializeMintInstruction(
      mint.publicKey, // Mint account to initialize.
      0, // Number of decimals for the token.
      feePayer.publicKey, // Authority allowed to mint new tokens.
      feePayer.publicKey, // Authority allowed to freeze token accounts.
      TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
    ),
    // !mark(1:10)
    createInitializeInstruction({
      programId: TOKEN_2022_PROGRAM_ID, // Program that owns the mint and metadata.
      metadata: mint.publicKey, // Mint account that stores the metadata.
      updateAuthority: feePayer.publicKey, // Authority allowed to update metadata later.
      mint: mint.publicKey, // Mint that the metadata describes.
      mintAuthority: feePayer.publicKey, // Signer authorizing metadata initialization for the mint.
      name: "Example Token", // Token name stored in metadata.
      symbol: "EXMPL", // Token symbol stored in metadata.
      uri: "https://example.com/token.json" // URI pointing to off-chain JSON metadata.
    }),
    // !mark(1:7)
    createUpdateFieldInstruction({
      programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
      metadata: mint.publicKey, // Mint account that stores the metadata.
      updateAuthority: feePayer.publicKey, // Authority allowed to update metadata fields.
      field: "description", // Custom metadata field to add.
      value: "Metadata stored on mint account" // Value stored for the custom metadata field.
    })
  ),
  [feePayer, mint],
  { commitment: "confirmed" }
);

const initialTokenMetadata = await getTokenMetadata(
  connection,
  mint.publicKey,
  "confirmed",
  TOKEN_2022_PROGRAM_ID
);

console.log(
  JSON.stringify(
    {
      mint: mint.publicKey,
      tokenMetadata: initialTokenMetadata
    },
    null,
    2
  )
);

const updateMetadataSignature = await sendAndConfirmTransaction(
  connection,
  new Transaction().add(
    // !mark(1:7)
    createUpdateFieldInstruction({
      programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
      metadata: mint.publicKey, // Mint account that stores the metadata.
      updateAuthority: feePayer.publicKey, // Authority allowed to update metadata fields.
      field: "name", // Base metadata field to update.
      value: "Example Token v2" // Updated value for the token name.
    }),
    // !mark(1:7)
    createUpdateMetadataPointerInstruction(
      mint.publicKey, // Mint account that stores the MetadataPointer extension.
      feePayer.publicKey, // Authority allowed to update the metadata pointer.
      mint.publicKey, // Account address that stores the metadata.
      [], // Additional signer accounts required by the instruction.
      TOKEN_2022_PROGRAM_ID // Program that owns the mint account.
    ),
    // !mark(1:7)
    createRemoveKeyInstruction({
      programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
      metadata: mint.publicKey, // Mint account that stores the metadata.
      updateAuthority: feePayer.publicKey, // Authority allowed to remove custom metadata.
      key: "description", // Custom metadata key to remove.
      idempotent: false // Fail if the custom metadata key does not exist.
    }),
    // !mark(1:6)
    createUpdateAuthorityInstruction({
      programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
      metadata: mint.publicKey, // Mint account that stores the metadata.
      oldAuthority: feePayer.publicKey, // Current authority allowed to change the update authority.
      newAuthority: null // Clear the update authority so metadata can no longer be changed.
    }),
    // !mark(1:4)
    createEmitInstruction({
      programId: TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
      metadata: mint.publicKey // Mint account that stores the metadata to emit.
    })
  ),
  [feePayer],
  { commitment: "confirmed" }
);

const updateMetadataTransaction = (await connection.getTransaction(
  updateMetadataSignature,
  {
    maxSupportedTransactionVersion: 0
  }
)) as any;
const emittedDataBase64 =
  updateMetadataTransaction?.meta?.returnData?.data?.[0];
if (!emittedDataBase64) {
  throw new Error("Expected token metadata return data");
}
const emittedTokenMetadata = unpackTokenMetadata(
  Buffer.from(emittedDataBase64, "base64")
);

const mintAccount = await getMint(
  connection,
  mint.publicKey,
  "confirmed",
  TOKEN_2022_PROGRAM_ID
);
const metadataPointer = getMetadataPointerState(mintAccount);
const tokenMetadata = await getTokenMetadata(
  connection,
  mint.publicKey,
  "confirmed",
  TOKEN_2022_PROGRAM_ID
);

console.log(
  JSON.stringify(
    {
      mint: mint.publicKey,
      metadataPointer,
      tokenMetadata,
      emittedTokenMetadata
    },
    null,
    2
  )
);
```

</CodeTabs>

### Rust

<CodeTabs storage="token-rs" flags="r">

```rust !! title="Rust"
use anyhow::{anyhow, Result};
use base64::prelude::{Engine as _, BASE64_STANDARD};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_client::rpc_config::RpcTransactionConfig;
use solana_commitment_config::CommitmentConfig;
use solana_sdk::{
    pubkey::Pubkey,
    signature::{Keypair, Signer},
    transaction::Transaction,
};
use solana_transaction_status_client_types::UiTransactionEncoding;
use solana_system_interface::instruction::create_account;
use spl_token_2022_interface::{
    extension::{
        metadata_pointer::{
            instruction::{
                initialize as initialize_metadata_pointer,
                update as update_metadata_pointer,
            },
            MetadataPointer,
        },
        BaseStateWithExtensions, ExtensionType, StateWithExtensions,
    },
    instruction::initialize_mint,
    state::Mint,
    ID as TOKEN_2022_PROGRAM_ID,
};
use spl_token_metadata_interface::{
    borsh::BorshDeserialize,
    instruction::{
        emit as emit_token_metadata, initialize as initialize_token_metadata,
        remove_key as remove_token_metadata_key,
        update_authority as update_token_metadata_authority,
        update_field as update_token_metadata_field,
    },
    state::{Field, TokenMetadata},
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = RpcClient::new_with_commitment(
        String::from("http://localhost:8899"),
        CommitmentConfig::confirmed(),
    );

    let fee_payer = Keypair::new();

    let airdrop_signature = client
        .request_airdrop(&fee_payer.pubkey(), 1_000_000_000)
        .await?;
    loop {
        let confirmed = client.confirm_transaction(&airdrop_signature).await?;
        if confirmed {
            break;
        }
    }

    let mint = Keypair::new();
    let max_token_metadata = TokenMetadata {
        update_authority: Some(fee_payer.pubkey()).try_into()?,
        mint: mint.pubkey(),
        name: "Example Token v2".to_string(),
        symbol: "EXMPL".to_string(),
        uri: "https://example.com/token.json".to_string(),
        additional_metadata: vec![(
            "description".to_string(),
            "Metadata stored on mint account".to_string(),
        )],
    };

    let mint_space =
        ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MetadataPointer])?;
    let mint_rent = client
        .get_minimum_balance_for_rent_exemption(mint_space + max_token_metadata.tlv_size_of()?)
        .await?;

    let create_mint_transaction = Transaction::new_signed_with_payer(
        &[
            // !mark(1:7)
            create_account(
                &fee_payer.pubkey(), // Account funding account creation.
                &mint.pubkey(), // New mint account to create.
                mint_rent, // Lamports funding the mint account rent.
                mint_space as u64, // Account size in bytes for the mint plus MetadataPointer.
                &TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
            ),
            // !mark(1:6)
            initialize_metadata_pointer(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
                &mint.pubkey(), // Mint account that stores the MetadataPointer extension.
                Some(fee_payer.pubkey()), // Authority allowed to update the metadata pointer later.
                Some(mint.pubkey()), // Account address that stores the metadata.
            )?,
            // !mark(1:7)
            initialize_mint(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
                &mint.pubkey(), // Mint account to initialize.
                &fee_payer.pubkey(), // Authority allowed to mint new tokens.
                Some(&fee_payer.pubkey()), // Authority allowed to freeze token accounts.
                0, // Number of decimals for the token.
            )?,
            // !mark(1:10)
            initialize_token_metadata(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the mint and metadata.
                &mint.pubkey(), // Mint account that stores the metadata.
                &fee_payer.pubkey(), // Authority allowed to update metadata later.
                &mint.pubkey(), // Mint that the metadata describes.
                &fee_payer.pubkey(), // Signer authorizing metadata initialization for the mint.
                "Example Token".to_string(), // Token name stored in metadata.
                "EXMPL".to_string(), // Token symbol stored in metadata.
                "https://example.com/token.json".to_string(), // URI pointing to off-chain JSON metadata.
            ),
            // !mark(1:7)
            update_token_metadata_field(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
                &mint.pubkey(), // Mint account that stores the metadata.
                &fee_payer.pubkey(), // Authority allowed to update metadata fields.
                Field::Key("description".to_string()), // Custom metadata field to add.
                "Metadata stored on mint account".to_string(), // Value stored for the custom metadata field.
            ),
        ],
        Some(&fee_payer.pubkey()),
        &[&fee_payer, &mint],
        client.get_latest_blockhash().await?,
    );
    client
        .send_and_confirm_transaction(&create_mint_transaction)
        .await?;

    let initial_mint_account = client.get_account(&mint.pubkey()).await?;
    let initial_mint_state = StateWithExtensions::<Mint>::unpack(&initial_mint_account.data)?;
    let initial_token_metadata = initial_mint_state.get_variable_len_extension::<TokenMetadata>()?;

    println!("Mint: {}", mint.pubkey());
    println!("Token Metadata: {:#?}", initial_token_metadata);

    let update_metadata_transaction = Transaction::new_signed_with_payer(
        &[
            // !mark(1:7)
            update_token_metadata_field(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
                &mint.pubkey(), // Mint account that stores the metadata.
                &fee_payer.pubkey(), // Authority allowed to update metadata fields.
                Field::Name, // Base metadata field to update.
                "Example Token v2".to_string(), // Updated value for the token name.
            ),
            // !mark(1:7)
            update_metadata_pointer(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.
                &mint.pubkey(), // Mint account that stores the MetadataPointer extension.
                &fee_payer.pubkey(), // Authority allowed to update the metadata pointer.
                &[], // Additional signer accounts required by the instruction.
                Some(mint.pubkey()), // Account address that stores the metadata.
            )?,
            // !mark(1:7)
            remove_token_metadata_key(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
                &mint.pubkey(), // Mint account that stores the metadata.
                &fee_payer.pubkey(), // Authority allowed to remove custom metadata.
                "description".to_string(), // Custom metadata key to remove.
                false, // Fail if the custom metadata key does not exist.
            ),
            // !mark(1:6)
            update_token_metadata_authority(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
                &mint.pubkey(), // Mint account that stores the metadata.
                &fee_payer.pubkey(), // Current authority allowed to change the update authority.
                None::<Pubkey>.try_into()?, // Clear the update authority so metadata can no longer be changed.
            ),
            // !mark(1:6)
            emit_token_metadata(
                &TOKEN_2022_PROGRAM_ID, // Program that owns the metadata.
                &mint.pubkey(), // Mint account that stores the metadata to emit.
                None, // Start offset for the emitted metadata slice.
                None, // End offset for the emitted metadata slice.
            ),
        ],
        Some(&fee_payer.pubkey()),
        &[&fee_payer],
        client.get_latest_blockhash().await?,
    );
    let update_metadata_signature = client
        .send_and_confirm_transaction(&update_metadata_transaction)
        .await?;

    let update_metadata_result = client
        .get_transaction_with_config(
            &update_metadata_signature,
            RpcTransactionConfig {
                encoding: Some(UiTransactionEncoding::Json),
                commitment: Some(CommitmentConfig::confirmed()),
                max_supported_transaction_version: Some(0),
            },
        )
        .await?;
    let emitted_data = BASE64_STANDARD.decode(
        update_metadata_result
        .transaction
        .meta
        .and_then(|meta| meta.return_data.map(|return_data| return_data.data.0))
        .ok_or_else(|| anyhow!("Expected token metadata return data"))?,
    )?;
    let emitted_token_metadata = TokenMetadata::try_from_slice(&emitted_data)?;

    let mint_account = client.get_account(&mint.pubkey()).await?;
    let mint_state = StateWithExtensions::<Mint>::unpack(&mint_account.data)?;
    let metadata_pointer = mint_state.get_extension::<MetadataPointer>()?;
    let token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;

    println!("Mint: {}", mint.pubkey());
    println!("Metadata Pointer: {:#?}", metadata_pointer);
    println!("Token Metadata: {:#?}", token_metadata);
    println!("Emitted Token Metadata: {:#?}", emitted_token_metadata);

    Ok(())
}
```

</CodeTabs>
