Real-time Streams
Push live updates from your backend to connected clients without polling. Perfect for AI responses, chat apps, and long-running tasks.
Why Streams?
Building modern apps means dealing with long-running tasks - AI responses that stream in word by word, file processing that takes time, or chat messages that need to appear instantly.
Without Streams, you'd need to:
- Build polling logic on the frontend
- Set up WebSocket infrastructure manually
- Manage connection states and reconnection
- Handle data synchronization yourself
With Motia Streams, you get all of this out of the box. Just define what data you want to stream, and Motia handles the rest.
Some Use Cases for Streams
- AI/LLM responses → Stream ChatGPT responses as they generate
- Chat applications → Real-time messaging and typing indicators
- Long processes → Video processing, data exports, batch operations
- Live dashboards → Real-time metrics and notifications
- Collaborative tools → Real-time updates across multiple users
Creating a Stream
Streams are just files. Create a .stream.ts file in your steps/ folder and export a config.
👉 That's it. Motia auto-discovers the stream and makes it available as context.streams.chatMessage in all your handlers.
Using Streams in Steps
Once you've defined a stream, you can use it in any Step through context.streams.
Stream Methods
Every stream has these methods:
| Method | What it does |
|---|---|
set(groupId, id, data) | Create or update an item |
get(groupId, id) | Get a single item |
delete(groupId, id) | Remove an item |
getGroup(groupId) | Get all items in a group |
send(channel, event) | Send ephemeral events (typing, reactions, etc.) |
Think of it like this:
groupId= Which room/conversation/userid= Which specific item in that roomdata= The actual data matching your schema
Real Example: Todo App with Real-Time Sync
Let's build a todo app where all connected clients see updates instantly.
This is a real, working example from the Motia Examples Repository. You can clone it and run it locally!
Step 1: Create the stream definition
Step 2: Create an API endpoint that uses streams
What happens here:
- Client calls
POST /todowith a description - Server creates the todo and calls
streams.todo.set('inbox', todoId, newTodo) - Instantly, all clients subscribed to the
inboxgroup receive the new todo - No polling, no refresh needed
👉 Every time you call streams.todo.set(), connected clients receive the update instantly. No polling needed.
Restricting Stream Access
Streams can now enforce authentication and authorization rules so that only approved clients can subscribe.
1. Configure streamAuth in motia.config.ts
Motia uses contextSchema to generate the StreamAuthContext type inside your project's types.d.ts and stores whatever authenticate returns as authContext on the WebSocket connection. Returning null means the client is anonymous.
2. Apply fine-grained rules with canAccess
Each stream can expose an optional canAccess function that receives the subscription info plus the StreamAuthContext value returned by your authenticate function.
canAccess can be synchronous or async. If it's not defined, Motia allows every client (even anonymous ones) to subscribe. If you remove the function from a stream that previously had one, Motia evaluates it out-of-process using the generated runner (useful for Python/Ruby streams).
3. Send tokens from the client
Provide an auth token when creating the stream client by embedding it in the WebSocket URL. Motia will read it in authenticate before authorizing subscriptions.
When you pass the token via the protocols prop Motia sends it as Sec-WebSocket-Protocol: Authorization,<token>, matching the Workbench Stream RBAC plugin (ActiveStreamConnection.tsx).
Using the browser/node clients directly:
On the server side Motia validates the token (via authenticate), stores the resulting context on the WebSocket, and calls canAccess before creating a subscription. Unauthorized clients receive an error event and no subscriptions are created.
Testing Streams in Workbench
Testing real-time features can be tricky. Workbench makes it easy.
How to test:
- Make sure your API Step returns the stream object:
- Open http://localhost:3000/endpoints
- Watch the stream update in real-time

👉 Workbench automatically detects stream responses and subscribes to them for you.
Using Streams in Your Frontend
Once you have streams working on the backend, connect them to your React app.
Install
Setup Provider
Wrap your app with the provider:
Subscribe to Stream Updates
How it works:
useStreamGroup()subscribes to all items in theinboxgroup- When server calls
streams.todo.set('inbox', todoId, newTodo), thetodosarray updates automatically - React re-renders with the new data
- Works across all connected clients!

👉 Every time you call createTodo(), connected clients receive the update instantly. No polling needed.
Ephemeral Events
Sometimes you need to send temporary events that don't need to be stored - like typing indicators, reactions, or online status.
Use streams.<name>.send() for this:
Difference from set():
set()→ Stores data, clients sync to itsend()→ Fire-and-forget events, not stored
Stream Adapters
Stream adapters control where and how stream data is stored. Motia provides default adapters that work out of the box, and distributed adapters for production deployments with multiple instances.
Default Adapter (File Storage)
No setup needed. Streams are stored in .motia/streams/ directory.
The default FileStreamAdapter is perfect for single-instance deployments, development, and testing. Real-time updates work seamlessly within a single instance.
Distributed Adapter (Redis)
For production deployments with multiple Motia instances, use Redis to synchronize streams across instances. This ensures all connected clients receive updates regardless of which instance handles the request:
Use distributed stream adapters (like Redis) when running multiple Motia instances. Without them, stream updates are only visible to clients connected to the instance that created them.
Your Step code stays the same:
The adapter handles the storage backend and synchronization - your application code doesn't change. All connected clients receive updates in real-time, regardless of which instance processes the request.
Remember
- Streams = Real-time state that clients subscribe to
- Every
set()call pushes updates to connected clients instantly - Use
send()for temporary events like typing indicators - Test in Workbench before building your frontend
- No polling needed - WebSocket connection handles everything
What's Next?
📦 State Management
Learn about persistent storage across Steps
🔄 Steps
Deep dive into building with Steps