| title | Canister Discovery |
|---|---|
| description | How icp-cli enables canisters to find each other through automatic canister ID injection across different environments. |
How icp-cli enables canisters to discover each other through automatic ID injection.
Canister IDs are assigned at deployment time and differ between environments:
| Environment | Backend ID |
|---|---|
| local | bkyz2-fmaaa-aaaaa-qaaaq-cai |
| staging | rrkah-fqaaa-aaaaa-aaaaq-cai |
| ic (mainnet) | xxxxx-xxxxx-xxxxx-xxxxx-cai |
Hardcoding IDs creates problems:
- Deploying to a new environment requires code changes
- Recreating a canister invalidates hardcoded references
- Sharing code with others fails because IDs don't match
icp-cli solves this by automatically injecting canister IDs as canister environment variables during deployment.
During icp deploy, icp-cli automatically:
- Collects all canister IDs in the current environment
- Creates a variable for each:
PUBLIC_CANISTER_ID:<canister-name>→<principal> - Injects all these variables into every canister in the environment
This means each canister receives the IDs of all other canisters, enabling any canister to call any other canister without hardcoding IDs.
Note: Variables are only updated for the canisters being deployed. If you deploy a single canister (
icp deploy backend), only that canister receives updated variables. When adding new canisters to an existing project, runicp deploywithout arguments to update all canisters with the complete set of IDs.
For an environment with backend, frontend, and worker canisters:
PUBLIC_CANISTER_ID:backend → bkyz2-fmaaa-aaaaa-qaaaq-cai
PUBLIC_CANISTER_ID:frontend → bd3sg-teaaa-aaaaa-qaaba-cai
PUBLIC_CANISTER_ID:worker → b77ix-eeaaa-aaaaa-qaada-cai
These variables are stored in canister settings, not baked into the WASM. The same WASM can run in different environments with different canister IDs.
When deploying multiple canisters:
icp deploycreates all canisters first (getting their IDs)- Then injects
PUBLIC_CANISTER_ID:*variables into all canisters - Then installs WASM code
All canisters can reference each other's IDs regardless of declaration order in icp.yaml.
When your frontend is deployed to an asset canister:
- The asset canister receives
PUBLIC_CANISTER_ID:*variables - It exposes them via a cookie named
ic_env, along with the network's root key (IC_ROOT_KEY) - Your frontend JavaScript reads the cookie to get canister IDs and root key
This mechanism works identically on local networks and mainnet — your frontend code doesn't need to change between environments.
- hello-world template — The template from
icp newdemonstrates this pattern. Look at the frontend source code to see how it reads the backend canister ID. - frontend-environment-variables example — A detailed example showing dev server configuration with Vite.
Use @icp-sdk/core to read the cookie:
import { getCanisterEnv } from "@icp-sdk/core/agent/canister-env";
interface CanisterEnv {
"PUBLIC_CANISTER_ID:backend": string;
IC_ROOT_KEY: Uint8Array; // Parsed from hex by the library
}
const env = getCanisterEnv<CanisterEnv>();For local development with a dev server, see the Local Development Guide.
Since all canisters receive PUBLIC_CANISTER_ID:* variables for every canister in the environment, backend canisters can discover each other's IDs at runtime.
Rust canisters can read the injected canister IDs using ic_cdk::api::env_var_value:
use candid::Principal;
let backend_id = Principal::from_text(
&ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:backend")
).unwrap();Motoko canisters can read canister environment variables using Runtime.envVar from the motoko-core package (v2.1.0+):
import Runtime "mo:core/Runtime";
import Principal "mo:core/Principal";
let ?backendIdText = Runtime.envVar("PUBLIC_CANISTER_ID:backend") else {
return #err("backend canister ID not set");
};
let backendId = Principal.fromText(backendIdText);Once you have the target canister ID, make calls using your language's CDK:
- Rust:
ic_cdk::callAPI - Motoko: Inter-canister calls
If you prefer not to use canister environment variables:
- Init arguments — Pass canister IDs as initialization parameters
- Configuration — Store IDs in canister state during setup
Beyond automatic PUBLIC_CANISTER_ID:* variables, you can define custom canister environment variables in icp.yaml. See the Environment Variables Reference for configuration syntax.
Ensure the target canister is deployed:
icp canister list # Check what's deployed
icp deploy # Deploy all canistersCanister environment variables are set automatically during icp deploy. If you're using icp canister install directly, variables won't be set. Use icp deploy instead.
Check which environment you're targeting:
icp canister list -e local # Local environment
icp canister list -e production # Production environment- Binding Generation — Type-safe canister interfaces
- Environment Variables Reference — Complete variable documentation
- Canister Settings Reference — Settings configuration
- Build, Deploy, Sync — Deployment lifecycle details
- Local Development — Frontend local dev setup