0.17 to 1.0 Migration Guide
Migrating from Motia v0.17.x to the Motia 1.0-RC Framework powered by iii.
This guide covers migrating from Motia v0.17.x to the Motia 1.0-RC framework. It is organized by area of concern so you can migrate incrementally.
iii is required
Motia now requires the iii engine to run. Install iii from iii.dev before proceeding with the migration. All adapter and infrastructure configuration is now done through iii via a config.yaml file -- the SDK itself no longer handles any of this.
MD Migration Guide
A Markdown version of this guide is available at motia/MIGRATION_GUIDE.md.
Configuration
Project Config
The old motia.config.ts (using defineConfig) is replaced by two files managed by iii:
| Concern | Old | New |
|---|---|---|
| Project config & plugins | motia.config.ts (defineConfig({...})) | Removed (handled by iii engine via config.yaml) |
| Module/adapter config | N/A | config.yaml (iii engine config) |
| Auth & hooks | streamAuth in motia.config.ts | motia.config.ts (simplified, exports only auth hooks) |
| Build externals | .esbuildrc.json | Removed |
| Workbench UI layout | motia-workbench.json | Removed (see Workbench, Plugins, and Console) |
Dev Command
| Old | New |
|---|---|
motia dev | iii |
motia build | motia build (unchanged) |
Files to Delete
motia-workbench.json.motia/directory — warning: this will delete any local stream and state data persisted by the old engine; back up first if needed
motia.config.ts is not deleted -- it is simplified. Remove the defineConfig wrapper, all plugin imports, and the plugins array. Keep only the authentication hook exports (see the "New" tab above).
Module System and Runtime
The new Motia does not enforce a specific module system or runtime. You are free to use CommonJS, ESM, Node.js, Bun, or any compatible runtime. The framework adapts to your project's setup.
Runtime Support
Motia now has first-class support for Bun in addition to Node.js. You can choose whichever runtime fits your project:
| Runtime | Dev Command Example | Production Example |
|---|---|---|
| Node.js | npx motia dev | node dist/index-production.js |
| Bun | bun run dist/index-dev.js | bun run --enable-source-maps dist/index-production.js |
Module System
You can use either CommonJS or ESM -- the choice is yours. If you want to adopt ESM (recommended for Bun compatibility and modern tooling), update your project:
If you prefer to stay on CommonJS, that works too. Motia does not force a migration.
Steps and Triggers
No more step types
This is the most important conceptual change in new Motia: there are no longer separate "step types". In the old version, you had API steps, Event steps, and Cron steps -- each with its own config type. In the new version, everything is just a Step. What used to determine the "type" of a step is now expressed through its triggers -- an array of trigger definitions that describe how and when the step is activated. A single step can have multiple triggers of different kinds (HTTP, queue, cron, state, stream).
Config Type Changes
| Old | New |
|---|---|
ApiRouteConfig | StepConfig |
EventConfig | StepConfig |
CronConfig | StepConfig |
type: 'api' | 'event' | 'cron' | triggers: [{ type: 'http' | 'queue' | 'cron' | 'state' | 'stream' }] |
emits: ['topic'] | enqueues: ['topic'] |
subscribes: ['topic'] | Moved into trigger: { type: 'queue', topic: '...' } |
Handler Type Changes
| Old | New |
|---|---|
Handlers['StepName'] | Handlers<typeof config> |
ctx.emit({ topic, data }) | ctx.enqueue({ topic, data }) |
Type Safety
The new version uses as const satisfies StepConfig for full type inference:
HTTP Triggers
In the old version these were "API steps" -- a dedicated step type with type: 'api'. In the new version, HTTP is just a trigger type (type: 'http') on a regular step.
Key differences:
type: 'api'is nowtype: 'http'inside a trigger object.method,path,bodySchema,responseSchemaall move inside the trigger.emitsbecomesenqueuesat the config level.emit()becomesenqueue()in the handler context.middlewareis removed from step config (see Middleware).- Config type changes from
ApiRouteConfigtoStepConfigwithas const satisfies.
HTTP Helper Shorthand
Queue Triggers (formerly Event Steps)
The concept of "event steps" that subscribe to topics no longer exists as a step type. Instead, subscribing to a topic is now a queue trigger on a regular step.
| Old | New |
|---|---|
type: 'event' | triggers: [{ type: 'queue', topic, input }] |
subscribes: ['topic'] | topic field inside trigger |
emits: ['topic'] | enqueues: ['topic'] |
input: schema | input: schema inside trigger (or wrap with jsonSchema()) |
emit({ topic, data }) | enqueue({ topic, data }) |
Using jsonSchema() Wrapper
When the input schema needs JSON schema conversion for the engine, use the jsonSchema() wrapper:
Cron Triggers
| Old | New |
|---|---|
type: 'cron' at config root | triggers: [{ type: 'cron', expression }] |
cron: '0 5 * * *' (5-field) | expression: '0 0 5 * * * *' (7-field, includes seconds and year) |
Handler: async ({ logger, emit }) | Handler: async (input, { logger, enqueue }) |
emit() | enqueue() |
Cron Expression Format
The new engine uses a 7-field cron expression:
| Old (5-field) | New (7-field) | Meaning |
|---|---|---|
0 5 * * * | 0 0 5 * * * * | Daily at 5:00 AM |
0 2 * * * | 0 0 2 * * * * | Daily at 2:00 AM |
*/5 * * * * | 0 */5 * * * * * | Every 5 minutes |
0 0 * * 0 | 0 0 0 * * 0 * | Weekly on Sunday at midnight |
Streams
Stream definitions remain similar but the access API has changed.
Stream Config
Stream Operations API
| Operation | Old | New |
|---|---|---|
| Get | streams.name.get(id, key) | streams.name.get(groupId, id) |
| Set | streams.name.set(id, key, value) | streams.name.set(groupId, id, value) |
| Update | N/A | streams.name.update(groupId, id, UpdateOp[]) |
| Delete | streams.name.delete(id, key) | streams.name.delete(groupId, id) |
The parameter naming changed from (id, key) to (groupId, id) to better reflect the data model: a stream is partitioned by groups, and within each group items are identified by id.
Atomic Updates with UpdateOp
| Type | Fields | Description |
|---|---|---|
set | path, value | Set a field to a value (overwrite) |
merge | path (optional), value | Merge an object into the existing value (object-only) |
increment | path, by | Increment a numeric field |
decrement | path, by | Decrement a numeric field |
remove | path | Remove a field entirely |
Migration Example
Stream Triggers
Steps can now react to stream changes. The handler receives a StreamWrapperMessage:
Where the event field contains one of:
{ type: 'create', data: TStreamData }-- a new item was created{ type: 'update', data: TStreamData }-- an existing item was updated{ type: 'delete', data: TStreamData }-- an item was deleted{ type: 'event', data: { type: string, data: TEventData } }-- a custom event
State
State provides key-value storage grouped by a namespace. The core get, set, and list operations remain the same. The new version introduces two additions: atomic updates via the update method, and state triggers.
Existing API (unchanged)
Atomic Updates with update()
New feature
The update() method eliminates race conditions from manual get-then-set patterns by performing atomic operations.
Uses the same UpdateOp interface as streams. See Streams for the full list of operations.
State Triggers
Brand new feature
State triggers enable powerful reactive patterns -- for example, triggering a step when a parallel merge completes, without polling or manual coordination.
The handler receives the state change event as its first argument, including new_value, old_value, item_id, and group_id.
Middleware
Old Approach
New Approach
The middleware field has been removed from step configs. Authentication is now handled at the engine level:
- Stream authentication is configured in
motia.config.tsviaauthenticateStream. - API authentication should be handled within the step handler itself, or via shared utility functions.
- Error handling (previously
coreMiddleware) should be handled within handlers using try/catch.
New Features
Multi-Trigger Steps
A single step can now respond to multiple trigger types:
The step() Helper
For multi-trigger steps, the step() helper provides ctx.getData() and ctx.match():
Conditional Triggers
Triggers can include a condition function that determines whether the step should execute:
Helper Functions
Shorthand helpers for creating triggers:
Migration Checklist
Project Setup
- Install the iii engine from iii.dev
- Create
config.yamlwith module definitions (stream, state, api, queue, cron, exec) - Create
motia.config.tsfor authentication hooks (if needed) - Simplify
motia.config.ts: removedefineConfig, all plugin imports, and thepluginsarray; keep only auth hook exports - Delete
motia-workbench.json - Delete
.motia/directory (warning: this will delete any local stream and state data persisted by the old engine; back up first if needed) - Update dev script from
motia devtoiii - Choose your runtime (Node.js or Bun) and module system (CommonJS or ESM)
Steps
- Replace all
ApiRouteConfig/EventConfig/CronConfigimports withStepConfig - Convert all step configs to use
triggers[]andenqueues[] - Add
as const satisfies StepConfigto all configs - Replace
Handlers['StepName']withHandlers<typeof config> - Rename all
emit()calls toenqueue() - Rename all
emitsconfig fields toenqueues - Move
subscribesinto queue triggers - Move
method,path,bodySchema,responseSchemainto HTTP triggers - Change
type: 'api'totype: 'http'in all triggers - Move
croninto cron triggers asexpression(and convert to 7-field format) - Remove
typefield from config root - Remove
middlewarefield from all step configs - Replace
virtualEmitswithvirtualEnqueues(format changes from[{ topic, label }]to['topic'])
Streams
- Update stream access calls:
get(id, key)toget(groupId, id) - Update stream access calls:
set(id, key, value)toset(groupId, id, value) - Replace read-modify-write patterns with
update(groupId, id, UpdateOp[])where possible - Add
onJoin/onLeavehooks to stream configs if real-time subscription auth is needed
State
- Adopt
state.update()withUpdateOp[]to replace manual get-then-set patterns - Consider using state triggers for reactive workflows
Middleware
- Extract authentication logic into shared utility functions
- Extract error handling logic into handler-level try/catch or wrapper functions
- Remove all
middlewareimports and references from step configs
Cron Expressions
- Convert all 5-field cron expressions to 7-field format (prepend seconds, append year)
- Rename
cronfield toexpressioninside trigger objects
Python (if applicable)
- Install
motiaas a standalone Python package (npm/Node.js no longer required) - Add a separate ExecModule entry in
config.yamlfor the Python runtime - Refer to the dedicated Python migration guide for step-level changes
Workbench and Plugins
- Delete
motia-workbench.json - Remove any
.ui.step.tsor noop step files used exclusively for workbench rendering - Remove any workbench plugin code (React/JSX components for workbench panels)
- Familiarize with the iii Console as the replacement for the Workbench
Python Runtime
Major change for Python developers
In the old Motia, Python steps were managed by the Node.js runtime. Python developers previously needed Node.js and npm installed. This is no longer the case. Python is now a fully independent runtime.
In the new Motia, runtimes are fully independent. There is a dedicated Motia Python SDK (motia-py) that runs as its own standalone process, communicating directly with the iii engine. Python developers no longer need Node.js, npm, or any JavaScript tooling whatsoever.
| Aspect | Old | New |
|---|---|---|
| Python execution | Spawned as child process by Node runtime | Independent process managed by iii engine |
| Node.js required for Python? | Yes | No |
| SDK | Single motia npm package handled both | Separate motia-py (Python) and motia (Node) packages |
| Configuration | Shared with Node steps | Own config.yaml ExecModule entry |
For Mixed Projects (Node + Python)
Configure separate ExecModule entries in config.yaml:
A dedicated migration guide for Python projects and steps will be provided in a separate document. This guide focuses on the Node.js/TypeScript migration path.
Workbench, Plugins, and Console
Workbench Replaced by iii Console
The Motia Workbench (the local visual flow editor, configured via motia-workbench.json) has been replaced by the iii Console. The console provides a richer experience for visualizing and managing your flows, traces, and infrastructure.
Refer to the iii quickstart documentation for iii Console installation instructions.
Workbench Plugins Sunset
Workbench plugins (custom UI panels and extensions rendered inside the Workbench) have been sunset and are no longer supported. If your project relied on workbench plugins, you will need to find alternative approaches for any custom UI functionality they provided.
- Delete any
.ui.step.tsor noop step files that were used exclusively for workbench rendering. - Remove any React/JSX workbench plugin code that is no longer needed.
OpenAPI Generation
OpenAPI spec generation from HTTP step schemas is planned but not yet available in the new Motia version. This section will be updated once the feature is released.