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 type | Motoko | Rust | JavaScript |
|---|---|---|---|
bool | Bool | bool | boolean |
nat | Nat | candid::Nat or u128 | BigInt |
int | Int | candid::Int or i128 | BigInt |
nat8 | Nat8 | u8 | number |
nat64 | Nat64 | u64 | BigInt |
int32 | Int32 | i32 | number |
float64 | Float | f64 | number |
text | Text | String | string |
blob | Blob | Vec<u8> | Uint8Array |
null | Null | () | null |
principal | Principal | candid::Principal | Principal |
opt T | ?T | Option<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 Ttip: The[value] | []representation is the raw IDL encoding. In practice, usefromNullable()andtoNullable()from@dfinity/utilsto convert betweenoptvalues 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-generationIf candid is omitted, the Motoko recipe auto-generates the interface from the source.
Rust canisters require a .did file, but you can generate it from your code instead of writing it manually. Add the export_candid! macro at the end of your lib.rs:
use ic_cdk::query;use ic_cdk::update;
#[query]fn hello(name: String) -> String { format!("Hello, {}!", name)}
#[update]fn world(name: String) -> String { format!("World, {}!", name)}
// Enable Candid exportic_cdk::export_candid!();Then extract the Candid interface using candid-extractor:
# Install the extractor (one-time)cargo install candid-extractor
# Build to Wasmcargo build --release --target wasm32-unknown-unknown --package my_canister
# Extract the .did filecandid-extractor target/wasm32-unknown-unknown/release/my_canister.wasm > my_canister.didReference the generated .did file in your icp.yaml:
canisters: - name: my_canister recipe: type: "@dfinity/rust@v3.2.0" configuration: package: my_canister candid: src/my_canister/my_canister.didAs with Motoko, the Rust recipe can also auto-extract the Candid interface from the compiled Wasm using candid-extractor if you omit the candid field. Providing an explicit file is recommended for stable APIs.
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;};type UserProfile = { name : Text; age : Nat32; email : ?Text;};use candid::CandidType;use serde::Deserialize;
#[derive(CandidType, Deserialize)]struct UserProfile { name: String, age: u32, email: Option<String>,}Variants
Candid variants model enumerations or tagged unions:
type Result = variant { ok : text; err : text;};type Result = { #ok : Text; #err : Text;};use candid::CandidType;use serde::Deserialize;
#[derive(CandidType, Deserialize)]enum MyResult { #[serde(rename = "ok")] Ok(String), #[serde(rename = "err")] Err(String),}Interacting with canister interfaces
From the command line
Use icp canister call to invoke methods using Candid textual syntax:
# Call a query methodicp canister call my_canister greet '("World")'
# Call an update method with a record argumenticp 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 typesconst 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
opttypes: old clients that don’t send them getnullby default. - Widen parameter types. Change a parameter to a supertype of its previous type (for example,
nattoint). - Narrow return types. Change a result to a subtype of its previous type (for example,
inttonat).
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:
setis a new method (safe to add).addwidens its parameter fromnattoint(supertype) and adds a return value (safe to extend).subtractadds an optional parameter (safe withopt).getnarrows its return type frominttonat(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):
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):
npx @icp-sdk/bindgenBy default, the CLI reads icp.yaml and generates bindings for all canisters. See the @icp-sdk/bindgen documentation for configuration options.
ic-cdk-bindgen generates Rust bindings from .did files at build time via a Cargo build script. This gives you typed functions for inter-canister calls.
Add it as a build dependency:
cargo add --build ic-cdk-bindgenCreate a build.rs that points to the callee’s .did file:
fn main() { ic_cdk_bindgen::Config::new("callee", "candid/callee.did") .dynamic_callee("PUBLIC_CANISTER_ID:callee") .generate();}Then include the generated module in your canister code:
#[allow(dead_code, unused_imports)]mod callee { include!(concat!(env!("OUT_DIR"), "/callee.rs"));}
#[ic_cdk::update]async fn invoke_callee() { let _result = callee::some_method().await;}The .dynamic_callee("PUBLIC_CANISTER_ID:callee") mode reads the canister ID from a canister environment variable at runtime. The same PUBLIC_CANISTER_ID:<name> variables that icp deploy injects (see canister discovery). For canisters with fixed IDs, use .static_callee(principal) instead.
For type selector configuration and advanced options, see the ic-cdk-bindgen documentation.
Candid tools
didc: the Candid CLI for checking .did files, encoding/decoding values, and testing subtype compatibility. Download from the Candid releases page.
| Command | What it does |
|---|---|
didc check service.did | Validate 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.did | Check 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
- Inter-canister calls: make inter-canister calls using Candid interfaces
- Calling from clients: call canisters from JavaScript frontends and agents
- Candid specification: full type reference and subtyping rules