Migration guide
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.
Motia 1.0 introduces breaking changes. Please read this guide carefully before upgrading.
Overview
Motia 1.0 is now powered by the iii engine , a Rust-based runtime that manages queues, state, streams, cron, and observability through a single iii-config.yaml configuration file.
Key changes
iii engine required — The Rust-based runtime replaces the old Node.js-based runtime
Configuration changes — motia.config.ts is replaced by iii-config.yaml
Unified Step model — All triggers (HTTP, queue, cron, state, stream) use the same Step primitive
Simplified state management — Direct access to state via ctx.state
Improved streams — Better WebSocket support and authentication
No more plugins — Plugin system replaced by iii modules
Installation
1. Install the iii engine
Install the iii engine before upgrading:
brew tap iii-dev/tap
brew install iii
2. Update Motia packages
TypeScript/JavaScript
Python
npm install motia@^1.0.0-rc
pip install motia==1.0.0rc26
Configuration migration
Old: motia.config.ts
In v0.17, configuration was handled by motia.config.ts:
import { defineConfig } from '@motiadev/core'
import bullmqPlugin from '@motiadev/plugin-bullmq/plugin'
import endpointPlugin from '@motiadev/plugin-endpoint/plugin'
export default defineConfig ({
plugins: [
bullmqPlugin ,
endpointPlugin ,
] ,
streamAuth: {
authenticate : async ( request ) => {
// Auth logic
},
} ,
})
New: iii-config.yaml
In v1.0, configuration is handled by iii-config.yaml:
modules :
- class : modules::api::RestApiModule
config :
port : 3111
host : 0.0.0.0
- class : modules::queue::QueueModule
config :
adapter :
class : modules::queue::adapters::InMemoryAdapter
- class : modules::stream::StreamModule
config :
port : 3112
auth_function : motia.stream.authenticate
- class : modules::state::StateModule
config :
adapter :
class : modules::state::adapters::KvStore
config :
store_method : file_based
file_path : ./data/state_store.db
- class : modules::cron::CronModule
- class : modules::observability::ObservabilityModule
config :
otlp_endpoint : http://localhost:4318
shell :
entrypoint : dist/index.js
stdio : inherit
Stream authentication
Stream auth is now in a separate motia.config.ts file:
import type { StreamAuthInput , StreamAuthResult } from 'motia'
export async function authenticate (
request : StreamAuthInput
) : Promise < StreamAuthResult | null > {
// Extract token from request
const token = request . headers [ 'authorization' ]?. replace ( 'Bearer ' , '' )
if ( ! token ) return null
// Validate token and return context
return {
userId: 'user-123' ,
permissions: [ 'read' , 'write' ],
}
}
Step migration
Unified config model
In v0.17, HTTP endpoints and event handlers were separate. In v1.0, everything is a Step.
import { endpoint } from '@motiadev/core'
export default endpoint ({
name: 'CreateUser' ,
method: 'POST' ,
path: '/users' ,
}, async ( req , ctx ) => {
return { status: 200 , body: { ok: true } }
} )
import { event } from '@motiadev/core'
export default event ({
name: 'ProcessUser' ,
topic: 'user.created' ,
}, async ( data , ctx ) => {
// Process user
} )
export const config = {
name: 'CreateUser' ,
triggers: [
{
type: 'http' ,
method: 'POST' ,
path: '/users' ,
}
],
enqueues: [ 'user.created' ]
}
export const handler = async ( req , { enqueue }) => {
await enqueue ({
topic: 'user.created' ,
data: { id: '123' }
})
return { status: 200 , body: { ok: true } }
}
export const config = {
name: 'ProcessUser' ,
triggers: [
{
type: 'queue' ,
topic: 'user.created' ,
}
],
}
export const handler = async ( input , ctx ) => {
// Process user
ctx . logger . info ( 'Processing user' , input )
}
State management
Old: Separate state plugin
In v0.17, state was accessed through a plugin:
import { getState } from '@motiadev/plugin-states'
const state = getState ( 'users' )
await state . set ( 'user-123' , { name: 'John' })
const user = await state . get ( 'user-123' )
New: Direct access via context
In v1.0, state is accessed directly through ctx.state:
export const handler = async ( input , { state }) => {
await state . set ( 'users' , 'user-123' , { name: 'John' })
const user = await state . get ( 'users' , 'user-123' )
}
Streams
Old: Stream configuration
import { defineStream } from '@motiadev/core'
export default defineStream ({
name: 'messages' ,
authenticate : async ( ctx ) => {
// Auth logic
} ,
})
New: Stream definition
import { Stream } from 'motia'
export const messages = new Stream ({
name: 'messages' ,
onJoin : async ( ctx ) => {
// User joined stream
},
onLeave : async ( ctx ) => {
// User left stream
},
})
Stream authentication is now global in motia.config.ts.
Middleware
Middleware syntax has changed slightly:
const authMiddleware = async ( req , ctx , next ) => {
if ( ! req . headers . authorization ) {
return { status: 401 , body: { error: 'Unauthorized' } }
}
return next ()
}
import type { ApiMiddleware } from 'motia'
const authMiddleware : ApiMiddleware = async ( req , ctx , next ) => {
if ( ! req . request . headers . authorization ) {
return { status: 401 , body: { error: 'Unauthorized' } }
}
return next ()
}
Note: Request is now req.request in v1.0.
New features in v1.0
State triggers
React to state changes automatically:
export const config = {
name: 'OnUserUpdate' ,
triggers: [
{
type: 'state' ,
}
],
}
export const handler = async ( input , ctx ) => {
const { group_id , item_id , old_value , new_value } = input
ctx . logger . info ( 'State changed' , { group_id , item_id , old_value , new_value })
}
Stream triggers
React to stream events:
export const config = {
name: 'OnMessageCreated' ,
triggers: [
{
type: 'stream' ,
streamName: 'messages' ,
groupId: 'room-123' ,
}
],
}
export const handler = async ( input , ctx ) => {
const { event , groupId , id } = input
if ( event . type === 'create' ) {
ctx . logger . info ( 'New message' , event . data )
}
}
Multi-trigger Steps
Steps can now have multiple triggers:
export const config = {
name: 'ProcessOrder' ,
triggers: [
{
type: 'http' ,
method: 'POST' ,
path: '/orders' ,
},
{
type: 'queue' ,
topic: 'order.created' ,
}
],
}
export const handler = async ( input , ctx ) => {
// Use pattern matching to handle different triggers
return ctx . match ({
http : async ( req ) => {
return { status: 200 , body: { ok: true } }
},
queue : async ( data ) => {
ctx . logger . info ( 'Processing order' , data )
},
})
}
Migration checklist
Install iii engine
brew install iii-dev/tap/iii
Update Motia packages
npm install motia@^1.0.0-rc
Migrate Steps
Convert endpoints and events to the unified Step model.
Update state access
Replace state plugin calls with ctx.state.
Update stream definitions
Convert stream configurations to the new Stream class.
Update middleware
Update middleware to use req.request instead of req.
Test your application
Start the iii engine and test all endpoints, queues, and workflows.
Getting help
If you encounter issues during migration:
Next steps
Core concepts Learn about the new Step model
Configuration Configure the iii engine
Examples Explore migration examples
API reference Review the new API