Skip to content


New feature (added in 2023.2.67)


Identifiers define opaque, typed identifiers.


When dealing with collections, it is often useful to be able to identify an element inside that collection by something other than its position.

Identifiers allow you to create strongly typed identifiers to support this use case.

A typical example is a collection of structs, where individual elements have no natural identity. You can create an identifier to store these structs inside a map.

struct Payment {
    amount: Number,
    paymentDate: LocalDate,
    processed: Boolean

identifier PaymentId;

protocol[issuer, payee] Iou() {
    var payments: Map<PaymentId, Payment> = mapOf<PaymentId, Payment>();

    permission[issuer] pay(amount: Number) returns PaymentId {
        var p = Payment(amount, now().toLocalDate(), processed = false);
        var id = PaymentId();
        payments = payments.with(id, p);
        return id;

    permission[payee] markProcessed(id: PaymentId) {
        var processedPayment = payments.getOrNone(id).getOrFail().copy(processed = true);
        payments = payments.with(id, processedPayment);


An identifier type is declared by the identifier keyword, followed by the type name, followed by a semicolon,;. Declaration of an identifier type is a top-level construct.

identifier MyId;


An identifier instantiation starts with the identifier's type name, followed by parentheses, similar to a function call. It takes no arguments.

When an identifier value is instantiated within NPL, it is assigned a random, unique value.

For example, given an identifier defined as:

identifier MyId;

creating a new typed identifier value looks like so:

var myId: MyId = MyId();
var yourId = MyId(); // type annotation is not required


An identifier parameter, field or variable is declared like any other variables with the user-defined identifier type name. Values of an identifier type can be used as a key or value in a Map. You can also return values of a user-defined identifier type.

An identifier type can be imported from other packages using the use import statement.

An identifier type can be annotated with @experimental and @deprecated annotations.



Values of a user-defined identifier type can be compared for equality, == and inequality, !=.

var myId: MyId = MyId();
var yourId: YourId = YourId();

function validComparison(myId: MyId, anotherMyId: MyId) returns Boolean -> {
    return myId == anotherMyId;

Note that you cannot compare values of different identifier types.

// compile ERROR, myId and yourId have different types
function invalidComparison(myId: MyId, yourId: YourId) returns Boolean -> {
    return myId == yourId;


While .toText() standard library method is not supported for the values of a user-defined identifier type, logging identifier values is still possible.