Dynamic IDL Fetching
Fetch Candid definitions from canister metadata or compile via didjs
The @ic-reactor/candid package enables dynamic canister interaction without compile-time IDL. Perfect for building tools, explorers, or any application that needs to interact with canisters discovered at runtime.
Most IC applications know which canisters they’ll interact with at build time. But some applications need to:
Dynamic IDL Fetching
Fetch Candid definitions from canister metadata or compile via didjs
CandidReactor
Extends Reactor with dynamic method registration and calling
CandidDisplayReactor
Dynamic Reactor with automatic display type transformations (bigint → string)
MetadataDisplayReactor
Display Reactor with visitor-based metadata for form building and result display
CandidFormVisitor
Generate form metadata from Candid IDL with schema, component, and render hints
Local Parsing
Bundled WASM parser for fast, offline Candid compilation, loaded on first use
npm install @ic-reactor/candid @ic-reactor/core @icp-sdk/core @tanstack/query-core@ic-reactor/parser is a dependency, so local parsing works out of the box.
The adapter imports it on the first parse and bundlers emit it as its own
chunk; if it cannot be loaded at runtime, the adapter falls back to the didjs
canister.
Unlike @ic-reactor/core and @ic-reactor/react (Node.js 18+), this package
requires Node.js 20.19.0 or later — the floor of its @noble/hashes
dependency, declared in the package’s engines field.
The easiest way to interact with canisters dynamically:
import { CandidReactor } from "@ic-reactor/candid"import { ClientManager } from "@ic-reactor/core"import { QueryClient } from "@tanstack/query-core"
const clientManager = new ClientManager({ queryClient: new QueryClient() })await clientManager.initialize()
// Create a reactor for any canisterconst reactor = new CandidReactor({ name: "ledger", canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", // ICP Ledger clientManager,})
// Fetch IDL from networkawait reactor.initialize()
// Now all standard Reactor methods work!const name = await reactor.callMethod({ functionName: "icrc1_name" })console.log(name) // "Internet Computer"For quick one-off calls, use the convenience methods:
// Query with inline Candid signatureconst balance = await reactor.queryDynamic({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", args: [{ owner: Principal.fromText("...") }],})
// With TanStack Query cachingconst cachedBalance = await reactor.fetchQueryDynamic({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", args: [{ owner }],})For more control over Candid fetching and parsing:
import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Fetch Candid sourceconst candidSource = await adapter.fetchCandidSource( "ryjl3-tyaaa-aaaaa-aaaba-cai")
// Parse to IDL factoryconst { idlFactory } = await adapter.parseCandidSource(candidSource)
// Or do both in one callconst definition = await adapter.getCandidDefinition( "ryjl3-tyaaa-aaaaa-aaaba-cai")| Class | Purpose |
|---|---|
CandidReactor |
Dynamic Reactor with raw Candid types |
CandidDisplayReactor |
Dynamic Reactor with display transformations (UI-friendly) |
MetadataDisplayReactor |
Display Reactor with form & result metadata generation |
CandidFormVisitor |
Generate form metadata from Candid IDL types |
CandidAdapter |
Low-level Candid fetching and parsing utilities |
Use CandidReactor when you want raw Candid types (bigint, Principal) and full Reactor experience.
Use CandidDisplayReactor when building UIs — it automatically converts bigint to string, Principal to string, and supports form validation.
Use MetadataDisplayReactor when building dynamic forms or result displays — it generates structured field metadata from Candid types.
Use CandidAdapter when you need fine-grained control over Candid fetching, or you’re building your own abstractions.
The adapter tries multiple methods to get a canister’s Candid source:
candid metadata path__get_candid_interface_tmp_hack (fallback) — query method on the canister// Automatic — local parser when it can be loaded, otherwise the didjs canister.// Returns a CandidDefinition ({ idlFactory, init? }).const parsed = await adapter.parseCandidSource(candidSource)
// Local only — faster, offline, requires @ic-reactor/parser.// Returns the compiled JavaScript source as a string.await adapter.loadParser()const jsSource = adapter.compileLocal(candidSource)