Handler & Context Migration Guide
Migrating HTTP step handlers to the new MotiaHttpArgs-based signature, and migrating from context-based state/enqueue/logger/streams to standalone imports.
This guide covers two major migration areas:
- HTTP handler signature changes -- moving from
(req, ctx)toMotiaHttpArgs-based{ request, response }destructuring, including SSE support. - Context API changes --
state,enqueue,logger, andstreamshave been removed fromFlowContextand are now standalone imports.
For a complete migration from Motia v0.17.x to 1.0-RC, see the full migration guide.
Context API Changes
What Changed
FlowContext (the second argument to handlers, commonly called ctx) no longer contains state, enqueue, logger, or streams. These are now standalone imports from 'motia' or from stream files.
| Aspect | Old | New |
|---|---|---|
| Logger | ctx.logger.info(...) | import { logger } from 'motia' then logger.info(...) |
| Enqueue | ctx.enqueue({ topic, data }) | import { enqueue } from 'motia' then enqueue({ topic, data }) |
| State | ctx.state.set(group, key, value) | import { stateManager } from 'motia' then stateManager.set(group, key, value) |
| Streams | ctx.streams.name.get(groupId, id) | import { myStream } from './my.stream' then myStream.get(groupId, id) |
New FlowContext Shape
After migration, FlowContext only contains:
Logger
Enqueue
State Manager
Streams
Streams are no longer accessed via ctx.streams. Instead, create a Stream instance in a .stream.ts file and import it into your steps.
Multi-Trigger Steps with Context
When using ctx.match(), logger, enqueue, and stateManager are imports -- ctx is only used for match(), traceId, and trigger:
HTTP Handler Changes
What Changed
HTTP step handlers now receive a MotiaHttpArgs object as their first argument instead of a bare request object. This object contains both request and response, enabling streaming patterns like SSE alongside standard request/response flows.
| Aspect | Old | New |
|---|---|---|
| First arg (TS/JS) | req (request object directly) | { request, response } (MotiaHttpArgs) |
| First arg (Python) | req (dict-like object) | request: ApiRequest or args: MotiaHttpArgs |
| Body access (TS/JS) | req.body | request.body |
| Path params (TS/JS) | req.pathParams | request.pathParams |
| Headers (TS/JS) | req.headers | request.headers |
| Body access (Python) | req.get("body", {}) | request.body |
| Path params (Python) | req.get("pathParams", {}).get("id") | request.path_params.get("id") |
| Return type (Python) | {"status": 200, "body": {...}} | ApiResponse(status=200, body={...}) |
| Middleware placement | Config root: middleware: [...] | Inside trigger: { type: 'http', ..., middleware: [...] } |
| Middleware first arg | req | { request, response } |
TypeScript / JavaScript
Standard HTTP Handler
Key changes:
- Import
loggerfrom'motia'instead of destructuring fromctx - Destructure
{ request }(or{ request, response }for SSE) from the first argument - Access
request.body,request.pathParams,request.queryParams,request.headers - Return value stays the same:
{ status, body, headers? }
Types
Multi-Trigger Steps
When using ctx.match(), the HTTP branch handler also receives MotiaHttpArgs:
Python
Standard HTTP Handler
Key changes:
- Import
ApiRequest,ApiResponse,loggerfrommotia - Use
http()helper for trigger definitions logger,enqueue, andstateManagerare standalone imports -- not accessed viactx- Access typed properties:
request.body,request.path_params,request.query_params,request.headers - Return
ApiResponse(status=..., body=...)instead of a plain dict
Python Types
Middleware
Placement Change
Middleware has moved from the config root into the HTTP trigger object.
Middleware Signature Change
Server-Sent Events (SSE)
SSE is enabled by the response object in MotiaHttpArgs. Instead of returning a response, you write directly to the stream.
SSE key points:
- Destructure both
requestandresponsefrom the first argument - Use
response.status()andresponse.headers()to configure the response - Write SSE-formatted data to
response.stream(TS/JS) orresponse.writer.stream(Python) - Call
response.close()when done streaming - Do not return a response object
Migration Checklist
Context API
- Replace
ctx.logger/context.loggerwithimport { logger } from 'motia' - Replace
ctx.enqueue/context.enqueuewithimport { enqueue } from 'motia' - Replace
ctx.state/context.statewithimport { stateManager } from 'motia' - Replace
ctx.streams.name/context.streams.namewithimport { myStream } from './my.stream' - Create
.stream.tsfiles withnew Stream(config)for each stream used - Remove
state,enqueue,logger,streamsfrom handler destructuring ofctx - Update handler signatures: if
ctxis only used for destructuring those removed properties, the second argument can be omitted entirely
TypeScript / JavaScript (HTTP)
- Change handler first argument from
(req, ctx)to({ request }, ctx)for all HTTP steps - Replace
req.bodywithrequest.body - Replace
req.pathParamswithrequest.pathParams - Replace
req.queryParamswithrequest.queryParams - Replace
req.headerswithrequest.headers - Move
middlewarearrays from config root into HTTP trigger objects - Update middleware functions: change
(req, ctx, next)to({ request }, ctx, next) - Update
ctx.match()HTTP handlers: change(request) =>to({ request }) =>
Python
- Add imports:
from motia import ApiRequest, ApiResponse, http(andFlowContextonly if your handler needsctx) - Use
http()helper in trigger definitions - Change handler signature to
handler(request: ApiRequest[Any]) -> ApiResponse[Any](or includectx: FlowContext[Any]when needed) - Replace
req.get("body", {})withrequest.body - Replace
req.get("pathParams", {}).get("id")withrequest.path_params.get("id") - Replace
req.get("queryParams", {})withrequest.query_params - Replace
req.get("headers", {})withrequest.headers - Return
ApiResponse(status=..., body=...)instead of plain dicts - For SSE: use
MotiaHttpArgsinstead ofApiRequest