Skip to main content
This guide walks you through creating, building, testing, and publishing your first Move package on Sui.

What You’ll Build

You’ll create a simple package that defines a Sword object with magical properties - a common starting point for understanding Sui’s object model.

Prerequisites

Create a New Package

1
Initialize the package
2
Create a new Move package:
3
sui move new my_first_package
cd my_first_package
4
This creates a directory structure:
5
my_first_package/
├── Move.toml
├── sources/
└── tests/
6
Understand the package manifest
7
Open Move.toml to see the package configuration:
8
[package]
name = "my_first_package"
version = "0.0.1"
edition = "2024.beta"

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

[addresses]
my_first_package = "0x0"
9
Create your first module
10
Create sources/sword.move:
11
module my_first_package::sword {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    /// A sword with magical properties
    public struct Sword has key, store {
        id: UID,
        magic: u64,
        strength: u64,
    }

    /// A forge for creating swords
    public struct Forge has key {
        id: UID,
        swords_created: u64,
    }

    /// Module initializer - called once when published
    fun init(ctx: &mut TxContext) {
        let admin = Forge {
            id: object::new(ctx),
            swords_created: 0,
        };
        // Transfer forge to publisher
        transfer::transfer(admin, tx_context::sender(ctx));
    }

    /// Create a new sword
    public fun new_sword(
        forge: &mut Forge,
        magic: u64,
        strength: u64,
        ctx: &mut TxContext
    ): Sword {
        forge.swords_created = forge.swords_created + 1;
        Sword {
            id: object::new(ctx),
            magic,
            strength,
        }
    }

    /// Accessor for sword magic
    public fun magic(sword: &Sword): u64 {
        sword.magic
    }

    /// Accessor for sword strength
    public fun strength(sword: &Sword): u64 {
        sword.strength
    }

    /// Transfer sword to recipient
    public fun transfer_sword(
        sword: Sword,
        recipient: address,
        _ctx: &mut TxContext
    ) {
        transfer::public_transfer(sword, recipient);
    }
}

Build Your Package

Compile the Move code:
sui move build
Successful output:
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_first_package

Add Tests

Create sources/sword_tests.move:
#[test_only]
module my_first_package::sword_tests {
    use my_first_package::sword::{Self, Forge, Sword};
    use sui::test_scenario;

    #[test]
    fun test_sword_creation() {
        let admin = @0xAD;
        let user = @0xB0B;

        // Initialize test scenario
        let mut scenario = test_scenario::begin(admin);

        // Admin initializes the module
        {
            sword::init_for_testing(scenario.ctx());
        };

        // Admin creates a sword
        scenario.next_tx(admin);
        {
            let mut forge = scenario.take_from_sender<Forge>();
            let sword = sword::new_sword(&mut forge, 10, 5, scenario.ctx());

            // Verify sword properties
            assert!(sword.magic() == 10, 0);
            assert!(sword.strength() == 5, 1);

            // Transfer to user
            sword::transfer_sword(sword, user, scenario.ctx());
            scenario.return_to_sender(forge);
        };

        // User receives the sword
        scenario.next_tx(user);
        {
            let sword = scenario.take_from_sender<Sword>();
            assert!(sword.magic() == 10, 2);
            scenario.return_to_sender(sword);
        };

        scenario.end();
    }
}
Note: Add this helper to sword.move for testing:
#[test_only]
public fun init_for_testing(ctx: &mut TxContext) {
    init(ctx);
}

Run Tests

Execute the test suite:
sui move test
Expected output:
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING my_first_package
Running Move unit tests
[ PASS    ] my_first_package::sword_tests::test_sword_creation
Test result: OK. Total tests: 1; passed: 1; failed: 0

Publish to Testnet

1
Get testnet SUI
2
Ensure you have gas for deployment:
3
sui client faucet
sui client gas
4
Publish the package
5
sui client publish --gas-budget 100000000
6
You’ll see output including:
7
Transaction Digest: <digest>
Published Objects:
  PackageID: 0x<package_id>
  Version: 1
Created Objects:
  ObjectID: 0x<forge_object_id>
  Owner: Account Address ( 0x<your_address> )
8
Save important values
9
Save the Package ID and Forge object ID - you’ll need them to interact with your package.

Interact with Your Package

Call the new_sword function:
sui client call \
  --package 0x<package_id> \
  --module sword \
  --function new_sword \
  --args 0x<forge_object_id> 42 7 \
  --gas-budget 10000000
This creates a sword with magic=42 and strength=7.

Understanding Key Concepts

Abilities

  • key: Object can be used as a key in global storage (has UID)
  • store: Object can be stored inside other objects
  • copy: Object can be copied
  • drop: Object can be dropped/destroyed implicitly

Module Initializer

The init function runs once when the package is published:
fun init(ctx: &mut TxContext) {
    // One-time setup code
}

Object Creation

All objects on Sui have a unique ID:
Sword {
    id: object::new(ctx),  // Creates unique UID
    magic,
    strength,
}

Next Steps

Common Issues

Build errors

If you see “unresolved import” errors, ensure dependencies in Move.toml are correct.

Test failures

Use sui move test --verbose for detailed output.

Publication fails

Check you have sufficient gas:
sui client gas

Build docs developers (and LLMs) love