Decorators

Decorators expose your class methods as canister entry points. Each decorator specifies how the method should be called and what permissions it has.

Available Decorators

@query

Read-only methods that cannot modify state. Fast execution with optional cross-canister calls.

@update

Read-write methods that can modify canister state. Full async support with cross-canister calls.

@init

Initialization method called once during canister deployment. Sets up initial state.

@postUpgrade

Called after canister upgrade. Used for state migration and restoration.

@preUpgrade

Called before canister upgrade. Used for cleanup and state validation.

@inspectMessage

Called before every @update method. Provides access control and validation.

@heartbeat

Called periodically (~every second). Not recommended - use timers instead.

Quick Comparison

Decorator State Access Async Replication Instruction Limit
@query read-only yes* possible 5B
@update read-write yes yes 40B
@init read-write no yes 300B
@postUpgrade read-write no yes 300B
@preUpgrade read-only no yes 300B
@inspectMessage read-only no none 200M
@heartbeat read-write yes yes 40B

*Only with composite: true option

Common Options

All decorators support these common options:

  • manual: Manual argument/return handling
  • hidden: Hide from Candid interface (except @preUpgrade, @inspectMessage, @heartbeat)

Basic Example

import { IDL, query, update, init } from 'azle';

export default class {
    counter: number = 0;
    owner: string = '';

    @init([IDL.Text])
    initialize(ownerName: string): void {
        this.owner = ownerName;
    }

    @query([], IDL.Nat)
    getCounter(): number {
        return this.counter;
    }

    @update([], IDL.Nat)
    increment(): number {
        this.counter += 1;
        return this.counter;
    }
}

@query

Read-only canister method. Cannot modify state.

  • State: read-only
  • Replication: possible
  • Async: yes with composite set to true
  • Instruction limit: 5_000_000_000
import { IDL, query } from 'azle';

export default class {
    counter: number = 0;

    @query([], IDL.Nat)
    getCounter(): number {
        return this.counter;
    }

    @query([IDL.Text], IDL.Text)
    echo(message: string): string {
        return `Echo: ${message}`;
    }
}

Composite Queries

import { IDL, query, call } from 'azle';

export default class {
    @query([], IDL.Text, { composite: true })
    async crossCanisterQuery(): Promise<string> {
        const result = await call('canister-id', 'method_name', {
            returnIdlType: IDL.Text
        });
        return result;
    }
}

@update

Read-write canister method. Can modify state.

  • State: read-write
  • Replication: yes
  • Async: yes
  • Instruction limit: 40_000_000_000
import { IDL, update } from 'azle';

export default class {
    counter: number = 0;

    @update([], IDL.Nat)
    increment(): number {
        this.counter += 1;
        return this.counter;
    }

    @update([IDL.Nat], IDL.Nat)
    setCounter(value: number): number {
        this.counter = value;
        return this.counter;
    }
}

@init

Canister initialization method. Called once during deployment.

  • State: read-write
  • Replication: yes
  • Async: no
  • Instruction limit: 300_000_000_000
import { IDL, init } from 'azle';

export default class {
    owner: string = '';
    initialized: boolean = false;

    @init([IDL.Text])
    initialize(ownerName: string): void {
        this.owner = ownerName;
        this.initialized = true;
    }
}

@postUpgrade

Called after canister upgrade. Used to restore state.

  • State: read-write
  • Replication: yes
  • Async: no
  • Instruction limit: 300_000_000_000 (shared with preUpgrade)
import { IDL, postUpgrade } from 'azle';

export default class {
    version: string = '1.0.0';

    @postUpgrade([IDL.Text])
    upgrade(newVersion: string): void {
        this.version = newVersion;
        console.log(`Upgraded to version ${newVersion}`);
    }
}

@preUpgrade

Called before canister upgrade. Used to save state.

  • State: read-only
  • Replication: yes
  • Async: no
  • Instruction limit: 300_000_000_000 (shared with postUpgrade)
import { IDL, preUpgrade } from 'azle';

export default class {
    counter: number = 0;

    @preUpgrade()
    saveState(): void {
        // Save critical state before upgrade
        console.log(`Current counter: ${this.counter}`);
    }
}

@inspectMessage

Called before every @update method. Can reject calls.

  • State: read-only
  • Replication: none
  • Async: no
  • Instruction limit: 200_000_000
import { IDL, inspectMessage, msgCaller } from 'azle';

export default class {
    owner: string = 'owner-principal-id';

    @inspectMessage()
    inspect(methodName: string): boolean {
        const caller = msgCaller();

        // Only allow owner to call sensitive methods
        if (methodName === 'sensitiveMethod') {
            return caller.toText() === this.owner;
        }

        return true; // Allow all other methods
    }

    @update([], IDL.Text)
    sensitiveMethod(): string {
        return 'Secret data';
    }
}

@heartbeat

Called periodically (~every second). Not recommended for most use cases.

  • State: read-write
  • Replication: yes
  • Async: yes
  • Instruction limit: 40_000_000_000

Note: Use setTimer and setTimerInterval instead of @heartbeat for most periodic tasks.

import { IDL, heartbeat } from 'azle';

export default class {
    heartbeatCount: number = 0;

    @heartbeat()
    periodicTask(): void {
        this.heartbeatCount += 1;
        console.log(`Heartbeat ${this.heartbeatCount}`);
    }
}

Manual Mode

All decorators support manual mode for advanced use cases:

import { IDL, query, msgArgData, msgReply, IDL as CandidIDL } from 'azle';

export default class {
    @query([], IDL.Text, { manual: true })
    manualQuery(): void {
        const args = msgArgData();
        const decodedArgs = CandidIDL.decode([IDL.Text], args);

        const result = `Processed: ${decodedArgs[0]}`;
        const encodedResult = CandidIDL.encode([IDL.Text], [result]);

        msgReply(encodedResult);
    }
}