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.

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 on-chain 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).

Literal values

Literals can appear as expression roots or as dynamic field name keys.

Scalars

SyntaxTypeExample
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 ']'

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

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