Skip to main content
Version: 0.65.0


LIGO is strongly and statically typed. This means that the compiler checks how your contract processes data, ensuring that each function's expectations are met. If it passes the test, your contract will not fail at run-time due to some inconsistent assumptions on your data. This is called type checking.

LIGO types are built on top of Michelson's type system.

Built-in types

For quick reference, you can find all the built-in types here.

Type aliases

Type aliasing consists of renaming a given type when the context calls for a more precise name. This increases readability and maintainability of your smart contracts. For example we can choose to alias a string type as an animal breed - this will allow us to communicate our intent with added clarity.

type breed = string;
let dog_breed: breed = "Saluki";

The above type definitions are aliases, which means that breed and string are interchangeable in all contexts.

Simple types

// The type account_balances denotes maps from addresses to tez
type account_balances = map<address, tez>;
let ledger: account_balances =
(list([["tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" as address, 10 as mutez]]));

Structured types

Often contracts require complex data structures, which in turn require well-typed storage or functions to work with. LIGO offers a simple way to compose simple types into structured types.

The first of those structured types is the record, which aggregates types as fields and indexes them with a field name. In the example below you can see the definition of data types for a ledger that keeps the balance and number of previous transactions for a given account.

// Type aliasing
type account = address;
type number_of_transactions = nat;
// The type account_data is a record with two fields.
type account_data = {
balance: tez,
transactions: number_of_transactions
// A ledger is a map from accounts to account_data
type ledger = map <account, account_data>;
let my_ledger : ledger =
["tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" as address,
{balance: 10 as mutez, transactions: 5 as nat}]]));

Complementary to records are the variant types, which are described in the section on pattern matching. Records are a product of types, while variant types are sums of types.


In certain cases, the type of an expression cannot be properly inferred by the compiler. In order to help the type checker, you can annotate an expression with its desired type. Here is an example:

type parameter =
| ["Claim"]
| ["Withdraw"];
type storage = {
owner : address,
goal : tez,
deadline : timestamp,
backers : map<address, tez>,
funded : bool
let back = ([param, store] : [unit, storage]) : [list<operation>, storage] => { // Annotation
let no_op = list([]);
if (Tezos.get_now() > store.deadline) {
return failwith ("Deadline passed.");
else {
return match(Map.find_opt (Tezos.get_sender(), store.backers), {
None: () => {
let backers = Map.update(Tezos.get_sender(), Some(Tezos.get_amount()), store.backers);
return [no_op, {, backers:backers}];
Some: x => [no_op, store]