suideveloper
All questions
Sui Move

What is the Publisher object on Sui?

November 5, 2026

The Publisher object, defined in sui::package, proves that its holder published a particular module and therefore has authority over the types defined there. You claim it once, in the module's init function, by handing over the module's one-time witness to package::claim. Framework features that configure behaviour for a type you own, such as Object Display and Kiosk TransferPolicy, take &Publisher as proof.

Definition

Move
module sui::package;

public struct Publisher has key, store {
    id: UID,
    package: String,
    module_name: String,
}

It stores the package address and module name as strings. Because it has key and store, it is a normal owned object: you can transfer it, keep it in another object, or burn it.

Claiming it

Move
module book::hero;

use sui::package;

public struct Hero has key, store { id: UID }

/// One-time witness for this module.
public struct HERO has drop {}

fun init(otw: HERO, ctx: &mut TxContext) {
    // Claim and send to the publisher in one call:
    package::claim_and_keep(otw, ctx);
}

claim_and_keep is shorthand for transfer::public_transfer(package::claim(otw, ctx), ctx.sender()). Use plain claim if you want to store the Publisher somewhere else or use it immediately, for example to create a Display<Hero> in the same init.

Since the OTW exists only once per module, there can be only one Publisher per module, but a package with several modules can have several.

Checking authority

Publisher has two checks:

Move
publisher.from_module<Hero>()   // Hero is defined in this Publisher's module
publisher.from_package<Hero>()  // Hero is defined in this Publisher's package

Both compare against the type's original package ID, so a Publisher keeps working for types after a package upgrade. There are also published_package() and published_module() getters, and burn_publisher to destroy it.

Where it's required

  • Display: display::new<T>(pub: &Publisher, ctx) creates a Display<T>, which tells wallets and explorers how to render objects of type T.
  • Kiosk: transfer_policy::new<T>(pub: &Publisher, ctx) creates the TransferPolicy<T> that governs trades of T in Kiosks.

Each of these functions checks internally that the Publisher is from the package that defines T, so a Publisher from one package can't configure another package's types.

Using it as an admin key

You can gate your own functions with &Publisher, but be careful: every published module can produce a Publisher, so the type alone proves nothing. Always check it:

Move
const ENotAuthorized: u64 = 0;

public fun admin_action(pub: &Publisher, /* ... */) {
    assert!(pub.from_module<Hero>(), ENotAuthorized);
    // ...
}

Forget the check and anyone holding any Publisher can call the function. For application permissions, a dedicated capability struct like AdminCap created in init is usually clearer and type-safe.

Gotchas

  • Keep it safe. Whoever holds the Publisher can create new Display and transfer policy objects for your types. Many teams store it with the deployer or a multisig address.
  • You can only claim it in init, because that's the only place the OTW exists. If you published without claiming it, you can't get one later for that module.
  • In tests, package::test_claim(otw, ctx) creates a Publisher without the OTW check.

Sources

More on sui move