Skip to main content

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 changesmotia.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

npm install motia@^1.0.0-rc

Configuration migration

Old: motia.config.ts

In v0.17, configuration was handled by motia.config.ts:
Old (v0.17)
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:
New (v1.0)
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:
motia.config.ts
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.
HTTP endpoint (old)
import { endpoint } from '@motiadev/core'

export default endpoint({
  name: 'CreateUser',
  method: 'POST',
  path: '/users',
}, async (req, ctx) => {
  return { status: 200, body: { ok: true } }
})
Event handler (old)
import { event } from '@motiadev/core'

export default event({
  name: 'ProcessUser',
  topic: 'user.created',
}, async (data, ctx) => {
  // Process user
})

State management

Old: Separate state plugin

In v0.17, state was accessed through a plugin:
Old (v0.17)
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:
New (v1.0)
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

Old (v0.17)
import { defineStream } from '@motiadev/core'

export default defineStream({
  name: 'messages',
  authenticate: async (ctx) => {
    // Auth logic
  },
})

New: Stream definition

New (v1.0)
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()
}

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

1

Install iii engine

brew install iii-dev/tap/iii
2

Update Motia packages

npm install motia@^1.0.0-rc
3

Create iii-config.yaml

Replace motia.config.ts with iii-config.yaml. See Configuration.
4

Migrate Steps

Convert endpoints and events to the unified Step model.
5

Update state access

Replace state plugin calls with ctx.state.
6

Update stream definitions

Convert stream configurations to the new Stream class.
7

Update middleware

Update middleware to use req.request instead of req.
8

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

Build docs developers (and LLMs) love