Sui Object Model
MCP tool: When available in your environment, also query the Sui documentation MCP server (
) for up-to-date answers. Use it for verification and for details not covered by these reference files.
Source constraint: All information in this skill is sourced exclusively from
docs.sui.io and
move-book.com. When extending or updating this skill, only pull from these two sources. Do not use third-party blogs, tutorials, or unofficial documentation.
Objects are the fundamental unit of storage on Sui. Every resource, asset, and piece of data onchain is an object. Unlike account-based blockchains where state lives in shared mappings inside contracts, Sui gives each piece of state its own identity, version, and owner. Transactions consume objects as inputs and produce modified versions as outputs.
This skill routes to focused reference files. Load only the ones relevant to the current task.
Reference files
ownership — Ownership Types and Versioning
Path:
Load when: asking about ownership types (address-owned, consensus-address-owned, shared, immutable, wrapped), choosing between shared and owned, parallel execution, consensus, Mysticeti, fastpath, object versioning, Lamport timestamps, or frontend access to shared objects.
Covers: all five ownership types with consensus implications, consensus-address-owned objects, shared object access mode optimization, frontend PTB access pattern, wrapped object behavior, Lamport timestamp versioning, fastpath vs consensus versioning.
transfers — Transferring and Deleting Objects
Path:
Load when: transferring objects, choosing between
and
, implementing custom transfer rules, using
, transfer-to-object, or deleting/destroying objects.
Covers: six core transfer functions (module-restricted vs public), custom transfer rules, transfer-to-object with
,
vs
, object deletion/unpacking pattern, dynamic field cleanup warning.
dynamic-fields-and-collections — Dynamic Fields and Collections
Path: dynamic-fields-and-collections.md
Load when: using dynamic fields, choosing between
and
, working with collections (
,
,
,
), or designing storage for large datasets.
Covers: dynamic field vs dynamic object field visibility, field naming, core API (add/borrow/remove/exists_), all collection types with decision table, cleanup requirements, system limits (256 KB object size, 2048 objects/txn).
patterns — Common Patterns and Derived Objects
Path:
Load when: implementing hot potato, capability, soulbound, inventory, or borrow patterns, or working with derived objects.
Covers: hot potato pattern (no-ability structs), capability pattern (AdminCap/TreasuryCap), borrow pattern with
module, soulbound objects, inventory pattern (ObjectBag), derived objects with deterministic IDs, derived objects vs dynamic fields comparison.
display — Object Display (V2)
Path:
Load when: setting up how objects render in wallets, explorers, or apps, working with Display templates, configuring NFT metadata, or migrating from Display V1.
Covers: V2
creation via
display_registry::new_with_publisher
,
for updates,
/
/
/
API,
template syntax with nested field access, common display properties, V1 to V2 migration.
Routing guide
| Task | Load |
|---|
| What is an object / object structure | SKILL.md only |
| Abilities: key, store, copy, drop | SKILL.md only |
| Ownership types, shared vs owned vs consensus-address-owned | ownership |
| Parallel execution, consensus, fastpath | ownership |
| Object versioning, Lamport timestamps | ownership |
| Wrapped objects and accessibility | ownership |
| Frontend access to shared objects | ownership |
| Transferring objects, transfer vs public_transfer | transfers |
| Custom transfer rules | transfers + patterns |
| Transfer to object, Receiving | transfers |
| Deleting / destroying objects | transfers |
| Dynamic fields, dynamic object fields | dynamic-fields-and-collections |
| Collections: Table, Bag, VecMap, LinkedTable | dynamic-fields-and-collections |
| Scalability, large collection design | dynamic-fields-and-collections |
| Hot potato pattern | patterns |
| Capability pattern, AdminCap | patterns |
| Borrow pattern, cap inside object | patterns |
| Soulbound / non-transferable objects | transfers + patterns |
| Inventory for arbitrary objects | patterns + dynamic-fields-and-collections |
| Derived objects | patterns |
| Object Display, NFT rendering | display |
| Full project / code review | all reference files |
Object structure
Every Sui object contains four components:
- Globally unique ID: A 32-byte identifier derived from the creation transaction digest plus a generation counter. The ID never changes across the object's lifetime.
- Version number: An 8-byte integer that increments with each modification. Uses Lamport timestamps: the new version for all objects a transaction touches is
1 + max(version of all input objects)
.
- Owner field: A 32-byte value designating access control (an address, another object's ID, or a sentinel for shared/immutable).
- Transaction digest: A 32-byte hash referencing the last transaction that modified the object.
Objects can be referenced three ways: by ID alone (query current state), by versioned ID (read historical state), or by full object reference containing ID + version + digest (transaction inputs, authenticated snapshot).
Defining objects in Move
A Sui object is a Move struct with the
ability and an
as the first field:
move
public struct Sword has key, store {
id: UID,
damage: u64,
element: String,
}
is the only way to create a
. You cannot call
directly.
Move abilities and objects
| Ability | What it controls |
|---|
| The struct is a Sui object. Must have as first field. Required for all onchain objects. |
| The struct can be stored inside other objects, and transferred by any module using . Without it, only the defining module can transfer the object. Adding permanently removes the ability to enforce custom transfer rules. |
| The struct can be duplicated. Cannot be used on objects — lacks , so any struct with cannot have . Used only on non-object structs (configs, event data, read-only values). |
| The struct can be silently discarded at end of scope. Cannot be used on objects — lacks , so any struct with cannot have . Used only on non-object structs (ephemeral receipts, events). Objects must always be explicitly unpacked to destroy. |
Object ability combinations
Because
has neither
nor
, objects (structs with
) can only combine
and
:
- : Only the defining module can transfer, share, or freeze. Use for custom transfer rules.
- : Any module can transfer, share, freeze, or wrap. Use for freely composable assets. Once is granted, you cannot re-add custom transfer restrictions.
Non-object struct combinations (no )
- : Can be stored as a field inside an object. Cannot exist independently.
- : A plain data struct for events, intermediate values, and configs.
- : Can be used as dynamic field names.
- No abilities: A hot potato. Must be consumed in the same transaction. See patterns reference file.
Rules
- is the only way to create a UID. The field must be first.
- Once is added to a type, custom transfer rules are permanently disabled.
- Once an object is shared, it cannot be converted back to address-owned.
- Always remove all dynamic fields before deleting a parent object — orphaned fields become permanently inaccessible.
- Accessing a nonexistent dynamic field (via , , or ) aborts the transaction. Use to check first.
- Wrapping and unwrapping can happen within the same transaction — a PTB can wrap an object and later unwrap it atomically.
- Common capabilities: , , . Always mention all three when discussing the capability pattern.
- Each entry is a separate storage operation — gas cost scales linearly with entries accessed per transaction.
- Prefer immutable references () on shared objects to maximize parallel execution.
- Do not use for collections larger than ~100 entries. Use instead.
Common mistakes
- Adding when you need custom transfer rules. Once granted, any module can call and your enforcement logic is bypassed permanently.
- Using for large collections. It has O(n) lookup and is stored inline, hitting the 256 KB object size limit quickly.
- Confusing with . Use when the child must remain queryable by ID in explorers. Use for plain values.
- Deleting an object without removing its dynamic fields first. Those fields become permanently inaccessible.
- Using the legacy module for new code. Use (Display V2). V1 is deprecated and will be decommissioned.
- Forgetting to
display_registry::share(display)
after setting fields. The display must be shared to be discoverable.