NOOP Steps
NOOP (No Operation) steps are a powerful feature in Motia that serve multiple purposes:
- Modeling external processes, webhooks and integrations
- Representing human-in-the-loop activities
- Creating custom visualizations in the workbench
- Testing flows during development
File Structure
NOOP steps require two files with the same base name:
stepName.step.ts- Contains the step configurationstepName.step.tsx- Contains the UI component (optional)
Step Configuration File (.ts)
<Tabs items={['TS', 'JS']}>
export const config: NoopConfig = {
type: 'noop',
name: 'My NOOP Step',
description: 'Description of what this step simulates',
virtualEmits: ['event.one', 'event.two'],
virtualSubscribes: [], // Required even if empty
flows: ['my-flow'],
}
```
module.exports = { config }
```
UI Component File (.tsx)
<Tabs items={['TS', 'JS']}>
export default function MyStep() {
return (
<div className="p-4 bg-gray-800 rounded-lg border border-gray-600 text-white">
<div className="text-sm font-medium">My Step UI</div>
{/* Your custom UI elements */}
<BaseHandle type="source" position={Position.Bottom} />
</div>
)
}
```
export default function MyStep() {
return (
<div className="p-4 bg-gray-800 rounded-lg border border-gray-600 text-white">
<div className="text-sm font-medium">My Step UI</div>
{/* Your custom UI elements */}
<BaseHandle type="source" position={Position.Bottom} />
</div>
)
}
```
Example: Webhook Testing
Here's a complete example of a NOOP step that simulates webhook events:
<Tabs items={['TS', 'JS']}>
export const config: NoopConfig = {
type: 'noop',
name: 'Webhook Simulator',
description: 'Simulates incoming webhook events',
virtualEmits: ['webhook.received'],
virtualSubscribes: [],
flows: ['webhook-flow'],
}
```
module.exports = { config }
```
<Tabs items={['TS', 'JS']}>
export default function WebhookSimulator() {
return (
<div className="p-4 bg-gray-800 rounded-lg border border-gray-600 text-white">
<div className="text-sm font-medium mb-2">Webhook Simulator</div>
<button
onClick={() => {
fetch('/api/webhook', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ event: 'test' }),
})
}}
className="px-3 py-1 bg-blue-600 rounded text-sm"
>
Trigger Webhook
</button>
<BaseHandle type="source" position={Position.Bottom} />
</div>
)
}
```
export default function WebhookSimulator() {
return (
<div className="p-4 bg-gray-800 rounded-lg border border-gray-600 text-white">
<div className="text-sm font-medium mb-2">Webhook Simulator</div>
<button
onClick={() => {
fetch('/api/webhook', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ event: 'test' }),
})
}}
className="px-3 py-1 bg-blue-600 rounded text-sm"
>
Trigger Webhook
</button>
<BaseHandle type="source" position={Position.Bottom} />
</div>
)
}
```
Representing External Processes
NOOP steps represent parts of your workflow that happen outside your system. Common examples include:
Webhook Callbacks
<Tabs items={['TS', 'JS']}>
typescript export const config: NoopConfig = { type: 'noop', name: 'Wait for Stripe Webhook', description: 'Waits for payment confirmation', virtualSubscribes: ['payment.initiated'], virtualEmits: ['/api/stripe/webhook'], flows: ['payment'], }
javascript const config = { type: 'noop', name: 'Wait for Stripe Webhook', description: 'Waits for payment confirmation', virtualSubscribes: ['payment.initiated'], virtualEmits: ['/api/stripe/webhook'], flows: ['payment'], }
Human Approvals
<Tabs items={['TS', 'JS']}>
typescript export const config: NoopConfig = { type: 'noop', name: 'Manager Review', description: 'Manager reviews request', virtualSubscribes: ['approval.requested'], virtualEmits: ['/api/approvals/submit'], flows: ['approval'], }
javascript const config = { type: 'noop', name: 'Manager Review', description: 'Manager reviews request', virtualSubscribes: ['approval.requested'], virtualEmits: ['/api/approvals/submit'], flows: ['approval'], }
External System Integration
<Tabs items={['TS', 'JS']}>
typescript export const config: NoopConfig = { type: 'noop', name: 'GitHub Webhook', description: 'Waiting for repository events', virtualSubscribes: ['repository.watched'], virtualEmits: ['/api/github/webhook'], flows: ['repo-automation'], }
javascript const config = { type: 'noop', name: 'GitHub Webhook', description: 'Waiting for repository events', virtualSubscribes: ['repository.watched'], virtualEmits: ['/api/github/webhook'], flows: ['repo-automation'], }
Physical Processes
<Tabs items={['TS', 'JS']}>
typescript export const config: NoopConfig = { type: 'noop', name: 'Order Fulfillment', description: 'Warehouse processes order', virtualSubscribes: ['order.placed'], virtualEmits: ['/api/warehouse/status'], flows: ['fulfillment'], }
javascript const config = { type: 'noop', name: 'Order Fulfillment', description: 'Warehouse processes order', virtualSubscribes: ['order.placed'], virtualEmits: ['/api/warehouse/status'], flows: ['fulfillment'], }
Visualization in Workbench
NOOP steps are visually represented in the Motia Workbench with the following characteristics:
- Distinct node representation with clear input/output handles
- Visual indicators for virtual event connections
- Status indicators for waiting states
- Clear visualization of external system dependencies
Custom UI
You can enhance your NOOP steps with custom React components for better visualization:
<Tabs items={['TS', 'JS']}>
export default (_: EventNodeProps) => {
return (
<div className="p-3 px-6 flex flex-col max-w-[300px] bg-blue-500 border-white rounded-full text-white border border-solid text-center text-sm">
<div>Custom Processing</div>
<BaseHandle type="target" position={Position.Top} />
<BaseHandle type="source" position={Position.Bottom} />
</div>
)
}
// customNode.step.ts
export const config: NoopConfig = {
type: 'noop',
name: 'Custom Process',
virtualEmits: ['/api/process/complete'],
virtualSubscribes: ['process.start'],
flows: ['custom-flow']
}
```
export default (_: EventNodeProps) => {
return (
<div className="p-3 px-6 flex flex-col max-w-[300px] bg-blue-500 border-white rounded-full text-white border border-solid text-center text-sm">
<div>Custom Processing</div>
<BaseHandle type="target" position={Position.Top} />
<BaseHandle type="source" position={Position.Bottom} />
</div>
)
}
// customNode.step.js
const config = {
type: 'noop',
name: 'Custom Process',
virtualEmits: ['/api/process/complete'],
virtualSubscribes: ['process.start'],
flows: ['custom-flow']
}
module.exports = {config};
```
Best Practices
| Category | Guidelines |
|---|---|
| File Organization | • Keep configuration and UI code in separate files • Use .step.ts for configuration• Use .step.tsx for UI components |
| UI Components | • Use functional React components • Include proper TypeScript types • Follow Tailwind's utility classes • Keep components minimal and focused • Design clear visual connection points • Always include BaseHandle components for flow connections |
| Configuration | • Always include virtualSubscribes (even if empty)• Use descriptive names for virtual events • Include clear descriptions • Use descriptive, action-oriented names |
| External Process Modeling | • Document expected timeframes and SLAs • Define all possible outcomes and edge cases • Use exact API route matching |
| Testing | • Create isolated test flows • Use realistic test data • Handle errors gracefully • Implement clear status indicators • Label test steps explicitly • Provide visual feedback for actions |
Component Reference
Core Imports
| Import | Purpose |
|---|---|
BaseHandle | A React component that renders connection points for nodes in the workflow. Used to define where edges (connections) can start or end on a node. |
EventNodeProps | (TypeScript only) Interface defining the properties passed to node components, including node data, selected state, and connection information. |
Position | (TypeScript only) Enum that specifies the possible positions for handles on a node (Top, Right, Bottom, Left). Used to control where connection points appear. |
Handle Placement
| Handle Type | Position |
|---|---|
| Input Handles | Position.Top |
| Output Handles | Position.Bottom |
| Flow Direction | Top to bottom |
Styling Guidelines
| Category | Guidelines |
|---|---|
| Colors | Use semantic colors to indicate state (success, error, pending) |
| States | • Implement clear visual indicators for active/inactive states • Use subtle animations for state transitions |
| Design System | • Follow your project's design tokens • Maintain consistent spacing and padding • Use standard border radiuses • Ensure high contrast for readability • Use consistent font sizes (14px-16px) |