Skip to main content
Move packages are the fundamental unit of code organization on Sui. Understanding their structure is essential for building well-organized smart contracts.

Package Anatomy

A typical Move package has this structure:
my_package/
├── Move.toml          # Package manifest
├── sources/           # Move source files
│   ├── module1.move
│   └── module2.move
├── tests/             # Test files (optional)
│   └── module1_tests.move
└── examples/          # Example code (optional)
    └── usage.move

Move.toml Manifest

The Move.toml file defines package metadata, dependencies, and addresses.

Basic Structure

From the coin example in the Sui codebase:
[package]
name = "Coins"
version = "0.0.1"
edition = "2024.beta"

[dependencies]
Sui = { local = "../../../crates/sui-framework/packages/sui-framework" }

[addresses]
examples = "0x0"

Package Section

[package]
name = "my_package"        # Package name
version = "1.0.0"          # Semantic version
edition = "2024.beta"      # Move edition

Dependencies

Local dependencies

[dependencies]
Sui = { local = "../sui-framework" }
MyLib = { local = "../my_lib" }

Git dependencies

[dependencies]
Sui = { 
    git = "https://github.com/MystenLabs/sui.git", 
    subdir = "crates/sui-framework/packages/sui-framework",
    rev = "framework/mainnet" 
}

Address Aliases

Define named addresses for your package:
[addresses]
my_package = "0x0"  # Placeholder, assigned at publish
admin = "0x1234..."  # Fixed address
In your code:
module my_package::example {
    // my_package is resolved from Move.toml
}

Module Structure

Modules are the building blocks of Move packages.

Basic Module

module package_name::module_name {
    // Imports
    use sui::object::{Self, UID};
    use sui::transfer;

    // Constants
    const MAX_SUPPLY: u64 = 1000000;
    const ENotAuthorized: u64 = 0;

    // Structs
    public struct MyObject has key {
        id: UID,
        value: u64,
    }

    // Functions
    public fun create(value: u64, ctx: &mut TxContext): MyObject {
        MyObject {
            id: object::new(ctx),
            value,
        }
    }

    // Tests
    #[test]
    fun test_create() {
        // Test code
    }
}

Module Initializer

The init function runs once when the package is published:
module my_package::example {
    public struct AdminCap has key {
        id: UID,
    }

    // Called once at publish time
    fun init(ctx: &mut TxContext) {
        let admin_cap = AdminCap {
            id: object::new(ctx),
        };
        transfer::transfer(admin_cap, ctx.sender());
    }
}

Organizing Code

Single vs Multiple Modules

Single module - Simple packages:
sources/
└── nft.move
Multiple modules - Complex packages:
sources/
├── nft.move           # Main NFT logic
├── marketplace.move   # Trading functionality  
├── royalties.move     # Royalty management
└── utils.move         # Shared utilities

Module Visibility

Public modules

Expose functionality to other packages:
module my_package::public_api {
    public fun do_something() { /* ... */ }
}

Internal modules

Keep implementation details private:
module my_package::internal {
    // Only accessible within my_package
    public(package) fun helper_function() { /* ... */ }
}

Friend Declarations

Allow specific modules to access package-private functions:
module my_package::core {
    // Declare friends
    friend my_package::admin;
    friend my_package::marketplace;

    // Only friends can call this
    public(package) fun privileged_operation() {
        // ...
    }
}

module my_package::admin {
    use my_package::core;

    public fun admin_action() {
        core::privileged_operation(); // Allowed
    }
}

Code Organization Patterns

Pattern 1: Core + Extensions

sources/
├── core.move       # Core data structures
├── admin.move      # Admin functions
├── user.move       # User-facing functions
└── events.move     # Event definitions

Pattern 2: Feature-Based

sources/
├── token.move           # Token implementation
├── staking.move         # Staking logic
├── governance.move      # Governance
└── treasury.move        # Treasury management

Pattern 3: Layer-Based

sources/
├── storage.move    # Data layer
├── logic.move      # Business logic
└── api.move        # Public API

Real Example: Coin Package

From examples/move/coin:
coin/
├── Move.toml
└── sources/
    ├── my_coin.move              # Basic coin
    ├── fixed_supply.move         # Fixed supply coin
    ├── deflationary_supply.move  # Deflationary coin
    └── regcoin.move              # Regulated coin

my_coin.move

module examples::my_coin {
    use sui::coin::{Self, TreasuryCap};

    public struct MY_COIN has drop {}

    fun init(witness: MY_COIN, ctx: &mut TxContext) {
        let (treasury, metadata) = coin::create_currency(
            witness,
            6,              // decimals
            b"MY_COIN",     // symbol
            b"",            // name
            b"",            // description
            option::none(), // icon_url
            ctx,
        );
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury, ctx.sender())
    }

    public fun mint(
        treasury_cap: &mut TreasuryCap<MY_COIN>,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext,
    ) {
        let coin = coin::mint(treasury_cap, amount, ctx);
        transfer::public_transfer(coin, recipient)
    }
}

Building Packages

Build command

sui move build

Build with docs

sui move build --doc

Check dependencies

sui move build --fetch-deps-only

Publishing Packages

Publish to network

sui client publish --gas-budget 100000000

Publish with custom addresses

Update Move.toml:
[addresses]
my_package = "0x<address>"
Then publish:
sui client publish --gas-budget 100000000

Package Upgrades

Sui supports package upgrades. See the Upgrade Packages guide for details.

Upgrade policies

  • Compatible: Only add new functions/structs
  • Additive: Can add abilities to structs
  • Incompatible: Breaking changes

Best Practices

1. One concern per module

Keep modules focused:
// Good
module my_package::user_profile { /* ... */ }
module my_package::user_badges { /* ... */ }

// Avoid
module my_package::everything { /* ... */ }

2. Use clear naming

module defi_protocol::liquidity_pool { /* ... */ }
module defi_protocol::swap_router { /* ... */ }

3. Organize imports

module my_package::example {
    // Standard library
    use std::string;
    use std::vector;

    // Sui framework
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::TxContext;

    // Package imports
    use my_package::utils;
    use my_package::events;
}

4. Document public APIs

/// Create a new NFT with the given attributes.
/// 
/// # Arguments
/// * `name` - The NFT name
/// * `description` - The NFT description
/// * `url` - The image URL
public fun mint_nft(
    name: vector<u8>,
    description: vector<u8>,
    url: vector<u8>,
    ctx: &mut TxContext
): NFT {
    // Implementation
}

5. Version your packages

[package]
name = "my_package"
version = "1.2.0"  # Use semantic versioning

Next Steps

Build docs developers (and LLMs) love