Getting Started
Supported Minecraft Script API versions
Kairo requires the stable Minecraft Script API:
@minecraft/server2.0.0 or higher@minecraft/server-ui2.0.0 or higher
Versions prior to 2.0.0 used a different initialization model (e.g. WorldInitialize instead of WorldLoad) and are not supported.
Install kairo
Add the kairo behavior pack to your world. It acts as the communication hub between all your addons.
Download kairo from GitHub Releases.
Kairo does not require per-world configuration. If multiple Kairo versions are present in the same world, Kairo chooses one active host automatically. Addon code should still depend only on @kairo-js/router; it does not need to know which Kairo pack instance is currently hosting.
Using kairo-router
Add kairo-router to your addon to start communicating with other addons.
Installation
pnpm add @kairo-js/router @kairo-js/properties@kairo-js/router is the runtime API for addon communication. @kairo-js/properties provides the AddonProperties type and manifest-friendly metadata shape used by router.init().
Kairo also publishes @kairo-js/utils. You usually do not need it to start using the router, but it is available for shared helpers such as semantic-version utilities, seeded random values, JSON parsing helpers, and TypeBox compilation utilities.
Declare your APIs in the startup event
All API registrations and hook declarations must happen inside router.beforeEvents.startup.
import { router } from '@kairo-js/router'
import type { AddonProperties } from '@kairo-js/properties'
import { properties } from './properties'
router.beforeEvents.startup.subscribe((ev) => {
// Register an API your addon provides
ev.addonApi.register<{ playerId: string }, { balance: number }>(
'economy/getBalance',
async ({ playerId }) => ({ balance: 100 }),
)
})
router.init(properties)Register Minecraft custom components
Minecraft custom components must also be registered during router.beforeEvents.startup. Use the native registries exposed on the startup event:
router.beforeEvents.startup.subscribe((ev) => {
ev.itemComponentRegistry.registerCustomComponent('my:item_component', {
onUse(event) {
console.log(event.source?.name)
},
})
ev.blockComponentRegistry.registerCustomComponent('my:block_component', {
onPlayerInteract(event) {
console.log(event.player?.name)
},
})
})Call other addons' APIs
// fire-and-forget
router.send('economy-addon', 'onTransaction', { amount: 50 })
// await result
const result = await router.request<{ balance: number }>(
'economy-addon',
'getBalance',
{ playerId: '...' },
)
if ('canceled' in result) {
console.log(result.reason)
} else {
console.log(result.balance)
}For the full API, see the kairo-router API Reference.
Required dependencies
Your addon's properties.ts must declare "kairo" as a required dependency. Omitting it causes router.init() to throw.
import type { AddonProperties } from '@kairo-js/properties'
export const properties: AddonProperties = {
id: 'my-addon',
// ...
dependencies: {
kairo: '^1.0.0',
// optional: 'kairo-database': '^2.0.0' — required for router.save/load/delete/has
},
}Use optionalDependencies for integrations that are not required for activation. For example, router.save(), router.load(), router.delete(), and router.has() require kairo-database to be listed in either dependencies or optionalDependencies.
Custom commands
Register custom commands through ev.customCommandRegistry inside router.beforeEvents.startup, rather than directly through Minecraft's native registry.
Minecraft normally rejects multiple custom commands with the same id, which makes side-by-side addon versions difficult: only one version can register the command. Kairo-router wraps command registration so duplicate native registrations are skipped safely, while Kairo routes command execution to the currently active addon version. This lets worlds switch addon versions without breaking commands that keep the same command id.
The command id and parameter type sequence are a compatibility contract. Once a command is released, do not change the ordered parameter types for that same command id. Renaming parameters is allowed because names are documentation-facing metadata; for example, changing target: string to player: string is compatible. Changing the type order, adding a required parameter before an existing one, or changing a parameter from string to entity is not compatible.
If you need an incompatible command signature, release it as a major-version change and warn users that older addon versions using the previous signature must be uninstalled. Alternatively, publish a new command id and keep the old command available for existing worlds.
Standalone mode
Pass { standalone: true } to router.init() to enable standalone activation. When kairo is not installed, the addon activates automatically — but only if its required dependencies contain nothing beyond kairo and kairo-database. This is useful for self-contained addons that optionally integrate with kairo's lifecycle management.
See RouterInitOptions for full details.
Initialization timing
After world load, kairo runs its own initialization — Discovery, Registration, and Activation — before firing addonActivate. This takes roughly 30–50 ticks depending on the number of addons installed.
This delay is intentional and mirrors Minecraft's own constraint: the Script API 2.0.0+ throws if you call world-related methods before WorldLoad completes. addonActivate serves the same purpose — it is your safe signal that the world is ready and all cross-addon APIs are available.
router.afterEvents.addonActivate.subscribe(() => {
// Safe to use world methods and call other addons' APIs here
})TIP
If you are familiar with writing vanilla Script API addons, think of addonActivate as your WorldLoad. The extra ticks are kairo's handshake cost, not wasted time.