How do strings work in Sui Move?
November 6, 2026
Move has no built-in string primitive. The standard library provides two wrappers around vector<u8>: std::string::String, which guarantees valid UTF-8 and is the one to use by default, and std::ascii::String, which guarantees ASCII and supports per-character operations. You create them from bytes with b"hello".to_string() (or string::utf8), and recent compilers also infer a plain "hello" literal as a String when the expected type is known. length() counts bytes, not characters.
Creating strings
use std::string::{Self, String};
// From a byte string: aborts if the bytes aren't valid UTF-8.
let a: String = b"Hello".to_string();
let b: String = string::utf8(b"Hello");
// Without aborting: returns Option<String>.
let c: Option<String> = b"\xFF".try_to_string(); // none
// Typed string literal (compiler support added in late 2025).
let d: String = "Hello";
let e: std::ascii::String = "TICKER";
// Numbers to strings: every unsigned integer type has to_string().
let n: String = 42u64.to_string();A string literal's type comes from context: it can become vector<u8>, string::String or ascii::String. If the compiler can't tell, it defaults to vector<u8> with a warning, which is why "Hello".to_string() doesn't compile. On an older toolchain without string literals, use b"...".to_string().
String has copy, drop and store, so you can put it in object fields freely.
Common operations
let mut s: String = b"Hello,".to_string();
s.append(b" World!".to_string()); // "Hello, World!"
s.append_utf8(b"!"); // append raw bytes
s.length(); // 14 (bytes)
s.is_empty(); // false
s.substring(0, 5); // "Hello"
s.index_of(&b"World".to_string()); // 7
s.insert(5, b" there".to_string());
s.as_bytes(); // &vector<u8>
s.into_bytes(); // vector<u8>, consumes sindex_of returns the string's length when there's no match, not an Option. Comparisons with == are byte-for-byte. sub_string and bytes still exist but are deprecated aliases for substring and as_bytes.
There's no formatting, interpolation, split or case conversion on UTF-8 strings. Build text by appending pieces.
UTF-8 pitfalls
length() returns bytes: "héllo" is 6 bytes, and an emoji is 4. substring and insert take byte indices and abort if an index lands in the middle of a multi-byte character. There's no way to index individual characters of a UTF-8 String.
ASCII strings
ascii::String fits identifiers, tickers and URLs. Every character is one byte, so it offers more operations:
use std::ascii;
let t: ascii::String = b"sui".to_ascii_string(); // aborts on non-ASCII
let upper = t.to_uppercase(); // "SUI"
let ok = t.all_characters_printable();
let utf8: String = t.to_string(); // always succeeds
let back = utf8.to_ascii(); // aborts if not ASCIIIt also has push_char, pop_char, to_lowercase and index_of. The Sui framework uses ascii::String for things like a coin's symbol.
Strings in transactions
When a function takes a String or ascii::String argument, you pass a normal string from the client; Sui converts the bytes and validates the encoding. With @mysten/sui:
tx.moveCall({
target: `${PACKAGE_ID}::profile::set_name`,
arguments: [tx.object(PROFILE_ID), tx.pure.string('Alice')],
})From the CLI, pass it as a quoted argument to sui client call. Strings stored in objects count toward object size limits and storage fees, so keep long text offchain (for example on Walrus) and store a reference.
Sources
- String, The Move Book
std::stringsource, MystenLabs/suistd::asciisource, MystenLabs/sui- Add string literals (#22870), MystenLabs/sui