suideveloper
All questions
Objects

What is the Object Display standard on Sui?

November 9, 2026

Object Display is Sui's standard for describing how objects of a type should look off-chain. The type's owner publishes a Display<T> object holding key-value templates such as name, description and image_url, and values like "{name} (lvl. {level})" get filled in from each object's fields. Wallets, explorers and marketplaces read that template instead of guessing from raw struct fields. The current version, Display V2, lives in sui::display_registry: there is exactly one shared Display<T> per type, at an ID derived from the system registry at 0xd.

How it works

You don't put metadata fields on every object. The display rules live in one place:

  • Display<T> is a shared object whose ID is derived from the DisplayRegistry (address 0xd) and the type T. Anyone, including full nodes, can work out that ID offline, so no event scanning is needed.
  • A separate owned DisplayCap<T> authorizes set, unset and clear. Change the template once and every object of that type renders differently, with no need to migrate the objects themselves.
  • If a type has no display, clients just show the raw fields.

The suggested keys are name, description, link, image_url, thumbnail_url, project_url and creator. The standard doesn't enforce any of them, so you can add your own.

Creating a display

The registry is a shared object, and init only receives the one-time witness and TxContext. So you create the display with a separate call right after publishing. This follows the Move Book example:

Move
module book::arena;

use std::string::String;
use sui::display_registry::{Self, DisplayRegistry};

public struct Hero has key {
    id: UID,
    class: String,
    level: u64,
}

/// Call once after publishing; a second call aborts because
/// the registry holds a single Display per type.
entry fun create_display(registry: &mut DisplayRegistry, ctx: &mut TxContext) {
    let (mut display, cap) = display_registry::new<Hero>(
        registry,
        internal::permit(),
        ctx,
    );

    display.set(&cap, "name", "{class} (lvl. {level})");
    display.set(&cap, "description", "One of the greatest heroes of all time.");
    display.set(&cap, "link", "https://example.com/hero/{id}");
    display.set(&cap, "image_url", "https://example.com/hero/{class}.jpg");

    display.share();
    transfer::public_transfer(cap, ctx.sender());
}

display_registry::new takes an internal::Permit<T>, which only the module that defines T can create. To create a display from outside that module, use new_with_publisher, which takes your Publisher object instead. Call the function with the registry's ID:

Terminal
sui client call --package <PACKAGE_ID> --module arena --function create_display --args 0xd

Template syntax

Values are format strings. {field} inserts a field, and dot paths reach nested structs ({metadata.description}). V2 adds more:

  • Indexing: {items[0u64]}. Numeric literals always need a type suffix.
  • Fallbacks: {display_name | name | 'Anonymous'}
  • Transforms: {created_at:ts} (ISO 8601 from Unix milliseconds), :hex, :base64, :json, :url
  • Dynamic fields: {parent->['color']} for a dynamic field, {parent=>['hat']} for a dynamic object field. Each load counts against a budget of 8 object loads by default (-> costs one, => costs two).

Use {{ to output a literal brace.

V1 displays and migration

The original sui::display module (V1) used owned Display<T> objects found through DisplayCreated events, and a type could have several of them. A system migration copied every existing V1 display into a V2 Display<T> with cap_id set to none. To manage a migrated display, claim its cap with display_registry::claim (which consumes your V1 object) or claim_with_publisher. You can then delete the old object with delete_legacy. The Display V2 announcement said the V1 sui::display::new APIs would stop being callable after July 31, 2026, so new code should use only display_registry.

Gotchas

  • Make the setup function entry, not public. A later upgrade can drop an entry function, but public signatures are frozen.
  • Keep the DisplayCap safe. Whoever holds it controls how your type looks in every wallet.
  • Rendering happens in RPC and indexers, not on-chain. Check the result in an explorer after you set the fields.

Sources

More on objects