iii-sdk. It handled file scanning, middleware wiring, trigger registration, and production bundling automatically. These conveniences came at a cost: they hid iii’s three core primitives — Workers, Functions, and Triggers — behind opaque abstractions that limited what you could build.
By moving to iii-sdk directly, you unlock the full power of the engine:
- Add new workers that register their own functions and triggers, enabling multi-worker orchestration across services, languages, and runtimes.
- Connect from the browser using
iii-browser-sdkwith Worker RBAC for secure, real-time frontends — no REST layer needed. See Use iii in the browser. - Treat Motia as one worker among many instead of a standalone monolith. Your existing Motia code becomes just another worker in a larger iii deployment.
- Understand the primitives directly. Working with
registerFunction,registerTrigger, andregisterWorkerbuilds a mental model that transfers across all iii SDKs and documentation.
Before diving into this migration, we recommend reading the iii documentation to understand Workers, Functions, and Triggers. The quickstart and Everything is a Worker pages are good starting points.
Step 1 — Update dependencies
Removemotia and add iii-sdk. If you need a production bundler, add esbuild as a dev dependency.
| Before (Motia) | After (iii-sdk) |
|---|---|
motia | iii-sdk@0.11.0 |
motia build | esbuild via bun run esbuild.config.ts |
motia dev && bun run dist/index.js | bun run --watch src/main.ts |
Step 2 — Initialize the SDK
Createsrc/lib/iii.ts. This replaces the implicit connection that Motia managed for you.
@/lib/iii
import { logger } from 'motia' in your codebase changes to import { logger } from '@/lib/iii'.
Step 3 — Migrate handlers
Motia auto-registered functions and triggers from exportedconfig objects. With iii-sdk you call registerFunction and registerTrigger directly.
HTTP
- Motia
- iii-sdk
HTTP with middleware
- Motia
- iii-sdk
Cron
- Motia
- iii-sdk
Queue (durable subscriber)
- Motia
- iii-sdk
iii.trigger instead of Motia’s enqueue:
State
- Motia
- iii-sdk
Stream
- Motia
- iii-sdk
Multiple triggers on one function
A function can have multiple triggers. This was possible in Motia via thetriggers array, and maps directly to multiple registerTrigger calls sharing the same function_id:
Stream and State operations
In Motia, you used theStream class and stateManager to read and write data. Under the hood, these called iii.trigger() with built-in function IDs (stream::get, state::set, etc.). With iii-sdk, you call iii.trigger() directly. See the Stream and State worker docs for the full list of operations.
- Stream
- State
Additional function IDs:
stream::update, stream::list_groups, stream::send for streams; state::update, state::list_groups for state.Stream lifecycle hooks (onJoin / onLeave)
In Motia, *.stream.ts files could define onJoin and onLeave callbacks inside the StreamConfig. The framework registered a single shared function for each hook and dispatched by stream name internally.
With iii-sdk, you register these as regular functions with stream:join and stream:leave triggers. The handler receives a StreamJoinLeaveEvent with { stream_name, group_id, id, context }.
- Motia
- iii-sdk
If you had multiple streams with different
onJoin / onLeave logic, dispatch by event.stream_name inside the handler or register separate function IDs per stream.Stream authentication (authenticateStream)
In Motia, motia.config.ts exported an authenticateStream function that ran during WebSocket upgrade. With iii-sdk, you register a function directly and reference its ID in the engine’s stream module config.
- Motia
- iii-sdk
config.yaml:
- Motia used
queryParams(camelCase); iii-sdk usesquery_params(snake_case). - No trigger registration is needed for auth — it is config-driven via
auth_function. - The
motia.config.tsfile is no longer needed; delete it.
Step 4 — Update request and response shapes
Request properties
Motia wrapped iii-sdk’s HTTP request into friendlier property names. With iii-sdk you receive the raw shape.| Property | Motia (input.*) | iii-sdk (input.*) |
|---|---|---|
| Route params | params | path_params |
| Query string | query | query_params |
| JSON body | body | body |
| Headers | headers | headers |
| HTTP method | method | method |
| Request stream | requestBody | request_body |
| Response stream | response | response |
query_params value types
In iii-sdk, query_params values are string | string[]. Use a helper to safely extract the first value:
Response shape
iii-sdk usesstatus_code instead of status. This applies to every handler return and middleware response.
Step 5 — Set up the entry point
Motia discovered.step.ts files automatically. With iii-sdk, you create a single entry point that imports all handler files as side effects.
Create src/main.ts:
registerFunction and registerTrigger calls at the module level, registering the function and its triggers with the engine.
File renaming
Rename all*.step.ts files to *.ts:
handlers/, routes/, rest/, or any layout that fits your project.
Step 6 — Development workflow
Replace Motia’s dev command with a directbun watcher. In your config.yaml, configure the worker under iii-exec:
Step 7 — Production build
Motia hadmotia build. Replace it with a plain esbuild config.
Add to package.json:
esbuild.config.ts:
bundle: true— all application code and npm dependencies are bundled into a single file.external: ['ws']— thewspackage has native addons and must remain innode_modulesat runtime.sourcemap: true— run production withbun run --enable-source-maps dist/index-production.jsso stack traces map back to TypeScript source.platform: 'node'— Node.js built-ins (fs,path,crypto, etc.) are treated as external.format: 'esm'— matches"type": "module"in yourpackage.json.
Migration checklist
- Remove
motiafrom dependencies, addiii-sdk@0.11.0 - Add
esbuildas a dev dependency - Create
src/lib/iii.tswithregisterWorkerandLogger - Rename all
*.step.tsfiles to*.ts - Replace all
import { ... } from 'motia'with iii-sdk imports - Convert every
config+handlerexport toregisterFunction+registerTriggercalls - Replace
statuswithstatus_codein every handler return - Replace
paramswithpath_paramsandquerywithquery_params - Replace
requestBodywithrequest_bodyin streaming handlers - Replace
StreamandstateManagerusage withiii.trigger()calls or custom wrappers - Migrate stream
onJoin/onLeavehooks toregisterFunction+registerTrigger(stream:join/stream:leave) - Migrate
authenticateStreamfrommotia.config.tsto a registered function and setauth_functioninconfig.yaml - Delete
motia.config.ts - Create
src/main.tswith side-effect imports for every handler - Replace
motia devwithbun run --watch src/main.tsin your dev config - Replace
motia buildwith an esbuild config - Test all endpoints, cron jobs, queue subscribers, and stream handlers