Skip to main content

Object Display V2 Syntax

Display V2 is a template language for rendering Sui Move object values into human-readable strings, JSON, or encoded representations. A Display object defines a set of key-value pairs where both keys and values are format strings.

Format strings​

A format string is a mix of literal text and expressions delimited by { and }.

Hello, {name}! You have {balance} SUI.

To output a literal brace, double it: {{ produces {, }} produces }.

Expressions​

Each expression inside {...} has 3 parts:

{ chain | alternate | ... : transform }
^^^^^ ^^^^^^^^^ ^^^^^^^^^
primary fallbacks output format
  • Chains: Separated by |, evaluated left-to-right. The first non-null result is used.
  • Transform: An optional output format prefixed with : that controls how the value renders.

If all chains evaluate to null, the entire format string evaluates to null.

Access chains​

A chain navigates into an object's data. It starts from either the root object or a literal value, then follows a sequence of accessors.

If you omit the root, the chain starts from the object currently being displayed. You can also refer to that root object explicitly with $self.

{name}                  — equivalent to {$self.name}
{$self} — the root object itself
{$self.id} — explicit access from the root object

Field access​

Access named fields with dot notation:

{name}                  — top-level field "name"
{inner.value} — nested field
{url.url.bytes} — deeply nested field

Positional access​

Access unnamed (positional) fields by numeric index:

{pos.0}                 — first positional field
{tuple.0.1} — nested positional access

Vector and array indexing​

Index into a vector or VecMap with [...]:

{items[0u64]}           — first element
{items[2u32]} — third element (any numeric type works)
{items[idx]} — use another field's value as the index
{items[ns[0u64]]} — nested: use element of `ns` as index into `items`

For VecMap<K, V>, the index matches against keys:

{scores[6u32]}          — look up key 6u32 in the VecMap, returns the value

Dynamic field access (->)​

Load a dynamic field from onchain storage:

{parent->['key']}       — dynamic field with string key 'key'
{parent->['key'].x} — access field 'x' on the loaded dynamic field value
{parent.id->['key']} — explicit UID access (equivalent if parent starts with UID)
{parent->[field]} — use a field's evaluated value as the dynamic field name

Each -> access costs 1 object load from the budget.

Dynamic object field access (=>)​

Load a dynamic object field (the value is a full Sui object):

{parent=>['key']}       — dynamic object field with string key 'key'
{parent=>['key'].x} — access field 'x' on the loaded object
{parent.id=>['key'].id} — load the object and read its UID

Each => access costs 2 object loads (1 for the wrapper Field, 1 for the object).

Derived object access (~>)​

Load a derived object from onchain storage using the parent object and a derived key:

{parent~>['key']}       — derived object with string key 'key'
{parent~>['key'].x} — access field 'x' on the loaded object
{parent.id~>['key'].id} — load the object and read its UID
{registry~>[$self]} — use the current object as the derived key

Each ~> access costs 1 object load from the budget.

Literal values​

Literals can appear as expression roots or as keys for dynamic field, dynamic object field, and derived object lookups.

Scalars​

SyntaxTypeExample
$selfCurrent object{$self.id}
true or falsebool{true}
42u8u8{255u8}
1000u16u16{1000u16}
100000u32u32{100000u32}
999u64u64{999u64}
123u128u128{123u128}
42u256u256{42u256}
@0x1abcaddress{@0x1abc}

Bare numbers like 42 are not valid. A type suffix is always required.

Strings​

SyntaxDescription
'hello'UTF-8 string (0x1::string::String)
b'hello'Byte string (UTF-8 encoded bytes, vector<u8>)
x'48656c6c6f'Hex byte string (vector<u8>)

Strings use single quotes. Access individual bytes with .bytes[i] (for string literals) or direct indexing [i] (for byte literals):

{'ABC'.bytes[0u64]}     — 65 (ASCII 'A')
{b'ABC'[1u64]} — 66 (ASCII 'B')

Vectors​

{vector[1u8, 2u8, 3u8]}             — inferred element type
{vector<u64>[5u64, 6u64]} — explicit element type
{vector<u32>} — empty vector with type annotation

Struct literals​

Positional fields:

{0x1::m::MyStruct(42u64, 'hello')}
{0x2::table::Table<address, u64>(a, b)} — with type parameters

Named fields:

{0x1::m::MyStruct { id: @0x123, value: 42u64 }}

Enum and variant literals​

{0x1::option::Option<u64>::Some#1(42u64)}          — variant name + index
{0x1::m::MyEnum::Variant#0(field1, field2)} — positional fields

The #N suffix after the variant name is the variant's discriminant index.

Alternates (fallbacks)​

Use | to provide fallback chains. If the first chain returns null (missing field, out-of-bounds index, and so on), the next alternate is tried:

{name | 'Unknown'}              — fall back to literal string
{preferred_name | name} — try preferred_name first
{a | b | c} — try three options in order
{bar | 42u64} — fall back to numeric literal

If all alternates fail, the expression evaluates to null, and the entire format string becomes null.

Transforms​

A transform is applied to the final value with :transform syntax. It controls how the value serializes into the output.

str (default)​

Renders the value as a human-readable string. This is the default when you do not specify a transform.

{amount}           — same as {amount:str}
{amount:str} — explicit
Value typeOutput
u8-u256Decimal number: 42
booltrue or false
address0x-prefixed canonical hex: 0x0000...1234
String or vector<u8>UTF-8 decoded string

hex​

Renders the value as lowercase hexadecimal, zero-padded to the type's width.

{value:hex}
Value typeOutput
u82 characters: 2a
u164 characters: 002a
u328 characters: 0000002a
u6416 characters: 000000000000002a
u12832 characters
u25664 characters
address64 characters (no 0x prefix)
vector<u8>Each byte as 2 hex characters

base64​

Base64-encodes the byte representation of the value.

{value:base64}                  — standard base64, with padding
{value:base64(nopad)} — without padding
{value:base64(url)} — URL-safe alphabet
{value:base64(url, nopad)} — URL-safe, without padding

bcs​

BCS-serializes the entire value, then base64-encodes the result. Supports the same modifiers as base64:

{value:bcs}                     — BCS bytes, standard base64
{value:bcs(url, nopad)} — BCS bytes, URL-safe base64 without padding

This is useful for aggregate types (structs, enums, vectors) that you cannot render with str or hex.

json​

Outputs the value as a structured JSON value (not a quoted string). This is only valid when the expression is the sole content of the format string.

{inner:json}
Value typeJSON output
booltrue or false
u8, u16, u32Number: 42
u64, u128, u256String: "42"
addressString: "0x0000...1234"
StringString: "hello"
vector<u8>String (base64): "AQID"
structObject: { "field": value, ... }
enumObject: { "@variant": "Name", "field": value, ... }
vectorArray: [v1, v2, ...]

timestamp (alias: ts)​

Interprets a numeric value as Unix milliseconds and formats as ISO 8601:

{created_at:ts}                 — "2023-04-12T17:00:00Z"
{1681318800000u64:ts} — literal timestamp

Only works with numeric types that fit in i64.

url​

Like str, but percent-encodes reserved URL characters (RFC 3986 unreserved set: A-Z a-z 0-9 - . _ ~):

{label:url}                     — "hello%20world" for "hello world"

Numeric and address types pass through unchanged because they contain no reserved characters.

Option auto-unwrapping​

Option<T> values are automatically unwrapped. If the option is Some(v), the access continues into v. If it is None, the chain evaluates to null.

{maybe_name}       — null if None, the string value if Some("...")

Enum variant field access​

When you access fields on an enum, the access succeeds only if the current variant has the requested field. Otherwise it evaluates to null. This allows a single Display definition to handle multiple variants gracefully:

enum Status {
Pending { message: String },
Active { progress: u32 },
Done { count: u128, timestamp: u64 },
}
("pending",  "message = {message}")
("active", "progress = {progress}")
("complete", "count = {count}, timestamp = {timestamp}")

For a Pending value, only the "pending" key produces output. The others are null.

Escaped braces​

To include literal { or } in the output, double them:

{{{ns[0u8]}, {ns[1u16]}, {ns[2u8]}}}

This outputs {2, 1, 0}. The outer {{ and }} are literal braces, while the inner {...} are expressions.

Display object structure​

A Display object is a list of (key, value) format string pairs. Both keys and values are parsed as format strings, allowing computed keys.

("name",        "{name}")
("description", "{desc | 'No description'}")
("image_url", "https://example.com/images/{image_id}")
("balance", "{amount:hex}")
("data", "{inner:json}")

Result (for a given object):

{
"name": "My NFT",
"description": "A cool NFT",
"image_url": "https://example.com/images/42",
"balance": "000000000000002a",
"data": { "x": 100, "y": 200 }
}

Keys must evaluate to unique, non-null strings. Values that fail to evaluate produce null (partial failure is allowed).

Limits​

Parsing and evaluation are bounded by configurable limits:

LimitDefaultDescription
max_depth32Maximum nesting depth during parsing
max_nodes32,768Maximum AST nodes across all format strings in a Display
max_loads8Maximum object loads (-> costs 1, => costs 2)

Exceeding any limit produces an error (TooDeep, TooBig, or TooManyLoads).

Formal grammar​

format   ::= strand*

strand ::= text | expr

text ::= part+
part ::= TEXT | '{{' | '}}'

expr ::= '{' chain ('|' chain)* (':' xform)? '}'

chain ::= (literal | IDENT) accessor*

accessor ::= '.' IDENT
| '.' NUM_DEC
| '[' chain ']'
| '->' '[' chain ']'
| '=>' '[' chain ']'
| '~>' '[' chain ']'

literal ::= self | address | bool | number | string | vector | struct | enum

self ::= '$' 'self'

address ::= '@' (NUM_DEC | NUM_HEX)
bool ::= 'true' | 'false'
number ::= (NUM_DEC | NUM_HEX) numeric
string ::= ('b' | 'x')? STRING
vector ::= 'vector' '<' type ','? '>' ('[' ']')?
| 'vector' ('<' type ','? '>')? array
array ::= '[' chain (',' chain)* ','? ']'
struct ::= datatype fields
enum ::= datatype '::' (IDENT '#')? NUM_DEC fields

fields ::= '(' chain (',' chain)* ','? ')'
| '{' named (',' named)* ','? '}'
named ::= IDENT ':' chain

type ::= 'address' | 'bool' | 'vector' '<' type '>' | numeric | datatype
datatype ::= NUM_HEX '::' IDENT ('<' type (',' type)* ','? '>')?
numeric ::= 'u8' | 'u16' | 'u32' | 'u64' | 'u128' | 'u256'

xform ::= 'str' | 'hex' | 'json' | 'timestamp' | 'url'
| 'base64' xmod?
| 'bcs' xmod?
xmod ::= '(' b64mod (',' b64mod)* ','? ')'
b64mod ::= 'url' | 'nopad'

Error reference​

ErrorCause
UnexpectedTokenSyntax error from an unexpected token during parsing
UnexpectedEosSyntax error from an unexpected end of input
TooBigAST node budget (max_nodes) exceeded
TooDeepNesting depth (max_depth) exceeded
TooManyLoadsObject load budget (max_loads) exceeded
TooMuchOutputOutput size limit exceeded during rendering
TransformInvalidTransform cannot be applied to the value type (for example, :hex on a struct)
VectorTypeMismatchMixed element types in a vector literal