suideveloper
All questions
Objects

What is the difference between dynamic fields and dynamic object fields?

November 9, 2026

Both attach named data to an object's UID at runtime. The difference is what happens to the value. A dynamic field (sui::dynamic_field) can hold any value with store. If that value is an object, it gets wrapped and can no longer be found by its ID in explorers, wallets or RPC queries. A dynamic object field (sui::dynamic_object_field) only accepts objects (key + store) and keeps them as real objects, still visible at their own ID. That costs a bit more, because each access loads two objects instead of one.

Side by side

dynamic_field dynamic_object_field
Value constraint store key + store
Can hold primitives and plain structs Yes No
Stored object visible at its own ID No (wrapped) Yes
Objects loaded per access 1 2 (field plus child)
Get child's ID without its type No dof::id(&uid, name)
Collections built on it Table, Bag, LinkedTable ObjectTable, ObjectBag

The function names are the same in both modules: add, borrow, borrow_mut, remove, exists, exists_with_type, remove_opt and replace. They also abort with the same error codes.

How they're stored

A dynamic field is a single hidden Field<Name, Value> object owned by the parent. Its ID is derived from the parent ID and the name, and the value lives inside it.

A dynamic object field stores the name wrapped in an internal Wrapper<Name> type. The Field holds only the child's ID, and the child object is then owned by that field object. That's why the child keeps its identity, why reading it takes an extra object load, and why the Wrapper type means a dynamic_field and a dynamic_object_field with the same name on the same parent never collide.

Example

Move
module example::character;

use std::string::String;
use sui::dynamic_field as df;
use sui::dynamic_object_field as dof;

public struct Character has key { id: UID }
public struct Hat has key, store { id: UID, color: String }
public struct Nickname has store, drop { value: String }

public fun equip_hat(c: &mut Character, hat: Hat) {
    // Hat stays visible on explorers at its own ID.
    dof::add(&mut c.id, b"hat", hat);
}

public fun set_nickname(c: &mut Character, value: String) {
    // Plain data: dynamic_field is the only option and the cheaper one.
    df::add(&mut c.id, b"nickname", Nickname { value });
}

public fun hat_id(c: &Character): Option<ID> {
    dof::id(&c.id, b"hat")
}

public fun unequip_hat(c: &mut Character): Hat {
    dof::remove(&mut c.id, b"hat")
}

Which one to use

  • Default to dynamic_field. The Move Book recommends it unless you need to find the child by ID. It's cheaper and works for any store value.
  • Use dynamic_object_field when the child is an asset people should still see and look up, for example an NFT equipped on a character, or items in a kiosk-like container that wallets and explorers should show.
  • Either way, you can't pass the child directly as a transaction input. A transaction that tries to use a dynamic object field child by ID is rejected. You always go through the parent (dof::borrow_mut(&mut parent.id, name)) or remove it first.

Offchain

In @mysten/sui v2, client.listDynamicFields({ parentId }) returns entries with $kind set to 'DynamicField' or 'DynamicObject'. Object entries carry a childId, and client.getDynamicObjectField({ parentId, name }) returns the child object itself. A dynamic object field child reports an ObjectOwner owner, which is the field object, not the parent directly. For the basics, see What are dynamic fields on Sui?.

Sources

More on objects