Skip to main content

Project Structure

Understanding how to organize your Motia project is crucial for building maintainable and scalable workflow applications. This guide covers the directory structure, file naming conventions, and Motia's automatic step discovery system.

Basic Project Structure

Here's what a typical Motia project looks like:

File Descriptions

FilePurposeTypeAuto-Generated
01-api-gateway.step.tsTypeScript API endpointUser Code-
02-data-processor.step.pyPython data processingUser Code-
03-send-notification.step.jsJavaScript automationUser Code-
custom-ui.step.tsxOptional UI componentUser Code-
package.jsonNode.js dependencies (if using JS/TS)Config-
requirements.txtPython dependencies (if using Python)Config-
tsconfig.jsonTypeScript config (if using TypeScript)Config-
types.d.tsType definitions for your projectGenerated✅ By TypeScript
motia-workbench.json🤖 Visual workflow positioningGenerated✅ By Motia
config.ymlOptional Motia configurationConfig-
The `steps/` and `src/` directories are the heart of your Motia application - this is where all your workflow logic lives. Motia automatically discovers and registers any file following the naming pattern from both directories. Location and nesting rules
  • Both the steps/ and src/ directories are supported at the project root (e.g., my-motia-project/steps or my-motia-project/src).
  • You can freely nest steps in subfolders under either directory (e.g., steps/aaa/a1.step.ts, src/bbb/ccc/c1.step.py).
  • Discovery is recursive inside both directories, so deeper folder structures for large apps are supported.
  • You can use either steps/, src/, or both directories in your project.

Automatic Step Discovery

**Key Concept: Automatic Discovery**

Motia will automatically discover and register any file that follows the .step. naming pattern as a workflow step. You don't need to manually register steps - just create a file with the right naming pattern and Motia will find it.

Discovery Rules

Motia scans your steps/ and src/ directories and automatically registers files as steps based on these rules:

  1. File must contain .step. or _step. in the filename (e.g., my-task.step.ts, my_task_step.py)
  2. File must export a config object defining the step configuration
  3. File must export a handler function containing the step logic
  4. File extension determines the runtime (.ts = TypeScript, .py = Python, .js = JavaScript)

When you run motia dev, Motia will:

  • Scan both the steps/ and src/ directories recursively
  • Find all files matching *.step.* in both directories
  • Parse their config exports to understand step types and connections
  • Register them in the workflow engine
  • Make them available in the Workbench

File Naming Convention

Motia uses this specific pattern for automatic step discovery:

[prefix-]descriptive-name.step.[extension]
The `.step.` part in the filename is **required** - this is how Motia identifies which files are workflow steps during automatic discovery.

Supported Languages & Extensions

LanguageExtensionExample Step FileRuntime
TypeScript.tsuser-registration.step.tsNode.js with TypeScript
Python.pydata-analysis.step.pyPython interpreter
JavaScript.jssend-notification.step.jsNode.js

Naming Examples by Step Type

Step TypeTypeScriptPythonJavaScript
API Endpoint01-auth-api.step.ts01-auth-api.step.py or auth_api_step.py01-auth-api.step.js
Event Handlerprocess-order.step.tsprocess-order.step.py or process_order_step.pyprocess-order.step.js
Cron Jobdaily-report.step.tsdaily-report.step.py or daily_report_step.pydaily-report.step.js
Data Processingtransform-data.step.tsml-analysis.step.py or ml_analysis_step.pydata-cleanup.step.js

Step Organization Patterns

<Tabs items={["Sequential", "Feature-Based", "Language-Specific"]}>

Sequential Flow Organization

Perfect for linear workflows where order matters:

StepLanguagePurpose
01-api-start.step.tsTypeScriptAPI endpoint
02-validate-data.step.pyPythonData validation
03-process-payment.step.jsJavaScriptPayment processing
04-send-confirmation.step.tsTypeScriptEmail service
05-cleanup.step.pyPythonCleanup tasks

Feature-Based Organization

Organize by business domains for complex applications:

Benefits:

  • Logical grouping by business domain
  • Easy to locate related functionality
  • Team ownership by feature area
  • Independent scaling and deployment

Language-Specific Organization

Group by programming language for team specialization:

Benefits:

  • Team specialization by language
  • Consistent tooling and patterns
  • Easy onboarding for language experts
  • Shared libraries and utilities

Language-Specific Configuration

TypeScript/JavaScript Projects

For Node.js-based steps, you'll need:

package.json
{
"name": "my-motia-app",
"version": "1.0.0",
"scripts": {
"dev": "motia dev",
"build": "motia build",
"start": "motia start"
},
"dependencies": {
"motia": "^0.5.12-beta.121",
"zod": "^3.24.4"
},
"devDependencies": {
"typescript": "^5.7.3",
"@types/node": "^20.0.0"
}
}
tsconfig.json (for TypeScript)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "dist"]
}

Python Projects

For Python-based steps:

requirements.txt
# Core Motia dependency
motia>=0.5.12

# Common dependencies
requests>=2.28.0
pydantic>=1.10.0

# Data processing (if needed)
pandas>=1.5.0
numpy>=1.21.0

Step Discovery Examples

Let's see how Motia discovers different step types:

Example 1: TypeScript API Step

steps/user-api.step.ts
import { ApiRouteConfig, Handlers } from 'motia'
import { z } from 'zod'

// Motia discovers this file because:
// 1. Filename contains '.step.'
// 2. Exports 'config' object
// 3. Has .ts extension -> uses TypeScript runtime
export const config: ApiRouteConfig = {
type: 'api',
name: 'user-api',
path: '/users',
method: 'GET',
emits: ['users.fetched'],
flows: ['user-management']
}

export const handler: Handlers['user-api'] = async (req, { emit }) => {
await emit({
topic: 'users.fetched',
data: { users: [] }
})

return {
status: 200,
body: { message: 'Users retrieved' }
}
}

Example 2: Python Event Step

steps/data-processor.step.py
# Motia discovers this file because:
# 1. Filename contains '.step.'
# 2. Exports 'config' dict
# 3. Has .py extension -> uses Python runtime

config = {
"type": "event",
"name": "data-processor",
"description": "Process incoming data with Python",
"subscribes": ["users.fetched"],
"emits": ["data.processed"],
"flows": ["user-management"]
}

async def handler(input_data, ctx):
"""Process the data"""
processed_data = {
"original": input_data,
"processed_at": ctx.utils.dates.now().isoformat(),
"count": len(input_data.get("users", []))
}

await ctx.emit({
"topic": "data.processed",
"data": processed_data
})

Example 3: JavaScript Automation Step

steps/send-notifications.step.js
// Motia discovers this file because:
// 1. Filename contains '.step.'
// 2. Exports 'config' object
// 3. Has .js extension -> uses Node.js runtime

export const config = {
type: 'event',
name: 'send-notifications',
description: 'Send notifications via multiple channels',
subscribes: ['data.processed'],
emits: ['notifications.sent'],
flows: ['user-management']
}

export const handler = async (input, { emit, logger }) => {
logger.info('Sending notifications', { data: input })

// Send email, SMS, push notifications, etc.
const results = await Promise.all([
sendEmail(input),
sendSMS(input),
sendPush(input)
])

await emit({
topic: 'notifications.sent',
data: {
results,
sent_at: new Date().toISOString()
}
})
}

async function sendEmail(data) { /* implementation */ }
async function sendSMS(data) { /* implementation */ }
async function sendPush(data) { /* implementation */ }

Auto-Generated Files

Some files in your Motia project are automatically generated:

  • types.d.ts - TypeScript generates this for type definitions
  • motia-workbench.json - Motia manages visual node positions in the Workbench

Multi-Language Project Example

Here's a real-world example showing how the three languages work together:

Architecture Breakdown

LayerLanguagePurposeExamples
API LayerTypeScriptFast API responses, type safetyProduct catalog, user auth, order management
Processing LayerPythonData processing, ML, analyticsInventory sync, recommendations, fraud detection
Automation LayerJavaScriptBusiness automation, workflowsEmail campaigns, fulfillment, customer support
Integration LayerMulti-languageExternal system connectionsPayment webhooks, ERP sync, social media

Language Strengths & When to Use

LanguageBest ForCommon Step TypesExample Use Cases
TypeScriptAPI endpoints, type safety, web integrationsAPI, Event, UIREST APIs, webhooks, data validation
PythonData science, ML, automation, integrationsEvent, CronData analysis, AI models, file processing
JavaScriptAutomation, integrations, general scriptingEvent, CronEmail automation, webhooks, social media

Discovery Troubleshooting

If Motia isn't discovering your steps:

Common Issues

<Tabs items={["Filename Issues", "Export Issues", "Location Issues"]}>

Missing .step. in filename

❌ **Won't be discovered:**
✅ **Will be discovered:**

Missing config export

❌ Won't be discovered
// No config export
export const handler = async () => {
console.log('This won't be found by Motia')
}
✅ Will be discovered
// Proper exports
export const config = {
type: 'event',
name: 'my-step',
subscribes: ['my-topic'],
emits: ['my-output'],
flows: ['my-flow']
}

export const handler = async (input, ctx) => {
// Motia will discover and register this step
}

File outside steps/ or src/ directory

❌ **Won't be discovered:**
✅ **Will be discovered:**

Note: Both steps/ and src/ directories are supported. Files must be in one of these directories to be discovered.

Discovery Verification

Check if your steps are discovered:

# Run Motia in development mode
motia dev

# Look for discovery logs:
# ✅ Discovered step: user-api (TypeScript)
# ✅ Discovered step: data-processor (Python)
# ✅ Discovered step: send-notifications (JavaScript)

Next Steps

Now that you understand how Motia discovers and organizes steps: