For AI agents: Documentation index at /llms.txt

Skip to content

Candid Interface

Candid is the interface description language for the Internet Computer. Every canister exposes its public API through a Candid .did file that describes which methods it offers, what arguments they accept, and what they return. Because Candid is language-agnostic, a Motoko canister, a Rust canister, and a JavaScript frontend can all communicate through the same interface without any manual serialization code.

Candid handles the binary encoding and decoding transparently. You work with native types in your language (String in Rust, Text in Motoko, string in JavaScript) and Candid maps them to a common type system for transport.

The .did file

A Candid service description defines the public interface of a canister. Here is a minimal example:

service : {
greet : (text) -> (text) query;
}

This declares a service with one method, greet, that takes a text argument, returns text, and can be called as a query (no consensus required). The query annotation tells the network this method only reads state: see Canisters: Query calls for details.

A more complete example with multiple methods:

service counter : {
inc : () -> ();
read : () -> (nat) query;
write : (nat) -> ();
}

Named types

When multiple methods share the same complex type, define it once and reuse it:

type Address = record {
street : text;
city : text;
zip_code : nat;
country : text;
};
service address_book : {
set_address : (name : text, addr : Address) -> ();
get_address : (name : text) -> (opt Address) query;
}

Candid uses structural typing: two type definitions with different names but the same structure are interchangeable. The named alias is purely for readability.

Init arguments

A service definition can require initialization arguments:

type InitArgs = record {
admin : principal;
token_name : text;
};
service : (InitArgs) -> {
get_name : () -> (text) query;
}

These arguments must be supplied when the canister is first deployed. They configure the canister’s initial state.

Type system

Candid has a fixed set of types that map to native types in each supported language. The table below shows the most commonly used types:

Candid typeMotokoRustJavaScript
boolBoolboolboolean
natNatcandid::Nat or u128BigInt
intIntcandid::Int or i128BigInt
nat8Nat8u8number
nat64Nat64u64BigInt
int32Int32i32number
float64Floatf64number
textTextStringstring
blobBlobVec<u8>Uint8Array
nullNull()null
principalPrincipalcandid::PrincipalPrincipal
opt T?TOption<T>[value] | []
vec T[T]Vec<T>Array
record { ... }{ field : T; ... }struct (with CandidType derive)Object
variant { ... }{ #tag : T; ... }enum (with CandidType derive){ tag: value }

JavaScript opt T tip: The [value] | [] representation is the raw IDL encoding. In practice, use fromNullable() and toNullable() from @dfinity/utils to convert between opt values and idiomatic JavaScript (value | undefined).

For the complete type reference, including subtyping rules, see the Candid specification.

Generating .did files

The Motoko compiler generates Candid descriptions automatically from your actor’s type signature. When you build with icp-cli, the .did file is placed in the build output directory. No manual authoring needed.

You can also provide a hand-written .did file by setting the candid field in icp.yaml. This is useful when you want an explicit API contract that is versioned independently of the implementation:

canisters:
- name: backend
recipe:
type: "@dfinity/motoko@v4.1.0"
configuration:
main: backend/app.mo
candid: backend/backend.did # Optional: overrides auto-generation

If candid is omitted, the Motoko recipe auto-generates the interface from the source.

Type mapping in practice

Records

Candid records map to structs in Rust and object-like types in Motoko:

type UserProfile = record {
name : text;
age : nat32;
email : opt text;
};

Variants

Candid variants model enumerations or tagged unions:

type Result = variant {
ok : text;
err : text;
};

Interacting with canister interfaces

From the command line

Use icp canister call to invoke methods using Candid textual syntax:

Terminal window
# Call a query method
icp canister call my_canister greet '("World")'
# Call an update method with a record argument
icp canister call my_canister set_address '("Alice", record { street = "123 Main St"; city = "Zurich"; zip_code = 8000; country = "CH" })'

From JavaScript

The JS SDK (@icp-sdk/core) translates Candid types into native JavaScript values. To call a canister from JavaScript, you need typed declarations generated from the .did file: see Binding generation below for how to set this up.

The generated declarations export a createActor function and an idlFactory that describes the interface:

import { createActor } from "./declarations/my_canister";
const canister = createActor(canisterId, { agentOptions: { host } });
// Call a method: arguments and return values are native JS types
const greeting = await canister.greet("World");
console.log(greeting); // "Hello, World!"

From another canister

When one canister calls another, Candid handles the argument encoding and response decoding transparently. See Inter-canister calls for how to make inter-canister calls in Motoko and Rust.

Safe interface upgrades

Candid defines subtyping rules that let you evolve a service’s interface without breaking existing clients. The safe changes are:

  • Add new methods. Existing clients simply don’t call them.
  • Add return values. Extend the result sequence: old clients ignore the extra values.
  • Remove trailing parameters. Shorten the parameter list: old clients still send the extra arguments, which are silently ignored.
  • Add optional parameters. Extend the parameter list with opt types: old clients that don’t send them get null by default.
  • Widen parameter types. Change a parameter to a supertype of its previous type (for example, nat to int).
  • Narrow return types. Change a result to a subtype of its previous type (for example, int to nat).

Example upgrade

This initial interface:

service counter : {
add : (nat) -> ();
subtract : (nat) -> ();
get : () -> (int) query;
}

Can safely evolve to:

type Timestamp = nat;
service counter : {
set : (nat) -> ();
add : (int) -> (new_val : nat);
subtract : (nat, trap_on_underflow : opt bool) -> (new_val : nat);
get : () -> (nat, last_change : Timestamp) query;
}

This upgrade is safe because:

  • set is a new method (safe to add).
  • add widens its parameter from nat to int (supertype) and adds a return value (safe to extend).
  • subtract adds an optional parameter (safe with opt).
  • get narrows its return type from int to nat (subtype) and adds a second return value.

Deprecating fields

To deprecate a record field without breaking existing clients, change its type to opt empty or reserved:

type UserProfile = record {
name : text;
middle_name : reserved; // Deprecated: ignored by current code
email : text;
};

Using reserved prevents future developers from accidentally reusing the field’s hash for a different purpose.

Binding generation

Once you have a .did file, generate type-safe client bindings so callers get compile-time type checking instead of working with raw Candid values.

@icp-sdk/bindgen generates TypeScript declarations from .did files. It supports both a CLI and a Vite plugin for automatic regeneration during development.

Vite plugin (recommended for frontend projects):

vite.config.ts
import { defineConfig } from "vite";
import { icpBindgen } from "@icp-sdk/bindgen/vite";
export default defineConfig({
plugins: [
icpBindgen(),
],
});

The plugin reads your icp.yaml, finds each canister’s .did file, and generates bindings into your source tree automatically when the dev server starts or when .did files change.

CLI (for non-Vite projects or CI):

Terminal window
npx @icp-sdk/bindgen

By default, the CLI reads icp.yaml and generates bindings for all canisters. See the @icp-sdk/bindgen documentation for configuration options.

Candid tools

didc: the Candid CLI for checking .did files, encoding/decoding values, and testing subtype compatibility. Download from the Candid releases page.

CommandWhat it does
didc check service.didValidate a .did file
didc encode '(42, "hello")'Encode a Candid value to hex
didc decode <hex>Decode binary Candid back to text
didc subtype new.did old.didCheck that new is a safe upgrade from old

Candid UI: a web interface for calling canister methods directly from a browser, generated automatically for every deployed canister. Useful for testing and debugging without writing frontend code. Access it at https://a4gq6-oaaaa-aaaab-qaa4q-cai.icp0.io/?id=<canister-id> for mainnet canisters.

Next steps