Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/docs/content/docs/blocks/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Blocks",
"pages": ["agent", "api", "condition", "function", "evaluator", "router"]
"pages": ["agent", "api", "condition", "function", "evaluator", "router", "workflow"]
}
231 changes: 231 additions & 0 deletions apps/docs/content/docs/blocks/workflow.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
---
title: Workflow
description: Execute other workflows as reusable components within your current workflow
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Step, Steps } from 'fumadocs-ui/components/steps'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { ThemeImage } from '@/components/ui/theme-image'

The Workflow block allows you to execute other workflows as reusable components within your current workflow. This powerful feature enables modular design, code reuse, and the creation of complex nested workflows that can be composed from smaller, focused workflows.

<ThemeImage
lightSrc="/static/light/workflow-light.png"
darkSrc="/static/dark/workflow-dark.png"
alt="Workflow Block"
width={300}
height={175}
/>

<Callout type="info">
Workflow blocks enable modular design by allowing you to compose complex workflows from smaller, reusable components.
</Callout>

## Overview

The Workflow block serves as a bridge between workflows, enabling you to:

<Steps>
<Step>
<strong>Reuse existing workflows</strong>: Execute previously created workflows as components within new workflows
</Step>
<Step>
<strong>Create modular designs</strong>: Break down complex processes into smaller, manageable workflows
</Step>
<Step>
<strong>Maintain separation of concerns</strong>: Keep different business logic isolated in separate workflows
</Step>
<Step>
<strong>Enable team collaboration</strong>: Share and reuse workflows across different projects and team members
</Step>
</Steps>

## How It Works

The Workflow block:

1. Takes a reference to another workflow in your workspace
2. Passes input data from the current workflow to the child workflow
3. Executes the child workflow in an isolated context
4. Returns the results back to the parent workflow for further processing

## Configuration Options

### Workflow Selection

Choose which workflow to execute from a dropdown list of available workflows in your workspace. The list includes:

- All workflows you have access to in the current workspace
- Workflows shared with you by other team members
- Both enabled and disabled workflows (though only enabled workflows can be executed)

### Input Data

Define the data to pass to the child workflow:

- **Single Variable Input**: Select a variable or block output to pass to the child workflow
- **Variable References**: Use `<variable.name>` to reference workflow variables
- **Block References**: Use `<blockName.response.field>` to reference outputs from previous blocks
- **Automatic Mapping**: The selected data is automatically available as `start.response.input` in the child workflow
- **Optional**: The input field is optional - child workflows can run without input data
- **Type Preservation**: Variable types (strings, numbers, objects, etc.) are preserved when passed to the child workflow

### Examples of Input References

- `<variable.customerData>` - Pass a workflow variable
- `<dataProcessor.response.result>` - Pass the result from a previous block
- `<start.response.input>` - Pass the original workflow input
- `<apiCall.response.data.user>` - Pass a specific field from an API response

### Execution Context

The child workflow executes with:

- Its own isolated execution context
- Access to the same workspace resources (API keys, environment variables)
- Proper workspace membership and permission checks
- Independent logging and monitoring

## Safety and Limitations

To prevent infinite recursion and ensure system stability, the Workflow block includes several safety mechanisms:

<Callout type="warning">
**Cycle Detection**: The system automatically detects and prevents circular dependencies between workflows to avoid infinite loops.
</Callout>

- **Maximum Depth Limit**: Nested workflows are limited to a maximum depth of 10 levels
- **Cycle Detection**: Automatic detection and prevention of circular workflow dependencies
- **Timeout Protection**: Child workflows inherit timeout settings to prevent indefinite execution
- **Resource Limits**: Memory and execution time limits apply to prevent resource exhaustion

## Inputs and Outputs

<Tabs items={['Inputs', 'Outputs']}>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Workflow ID</strong>: The identifier of the workflow to execute
</li>
<li>
<strong>Input Variable</strong>: Variable or block reference to pass to the child workflow (e.g., `<variable.name>` or `<block.response.field>`)
</li>
</ul>
</Tab>
<Tab>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Response</strong>: The complete output from the child workflow execution
</li>
<li>
<strong>Child Workflow Name</strong>: The name of the executed child workflow
</li>
<li>
<strong>Success Status</strong>: Boolean indicating whether the child workflow completed successfully
</li>
<li>
<strong>Error Information</strong>: Details about any errors that occurred during execution
</li>
<li>
<strong>Execution Metadata</strong>: Information about execution time, resource usage, and performance
</li>
</ul>
</Tab>
</Tabs>

## Example Usage

Here's an example of how a Workflow block might be used to create a modular customer onboarding process:

### Parent Workflow: Customer Onboarding
```yaml
# Main customer onboarding workflow
blocks:
- type: workflow
name: "Validate Customer Data"
workflowId: "customer-validation-workflow"
input: "<variable.newCustomer>"

- type: workflow
name: "Setup Customer Account"
workflowId: "account-setup-workflow"
input: "<Validate Customer Data.response.result>"

- type: workflow
name: "Send Welcome Email"
workflowId: "welcome-email-workflow"
input: "<Setup Customer Account.response.result.accountDetails>"
```

### Child Workflow: Customer Validation
```yaml
# Reusable customer validation workflow
# Access the input data using: start.response.input
blocks:
- type: function
name: "Validate Email"
code: |
const customerData = start.response.input;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(customerData.email);

- type: api
name: "Check Credit Score"
url: "https://api.creditcheck.com/score"
method: "POST"
body: "<start.response.input>"
```

### Variable Reference Examples

```yaml
# Using workflow variables
input: "<variable.customerInfo>"

# Using block outputs
input: "<dataProcessor.response.cleanedData>"

# Using nested object properties
input: "<apiCall.response.data.user.profile>"

# Using array elements (if supported by the resolver)
input: "<listProcessor.response.items[0]>"
```

## Access Control and Permissions

The Workflow block respects workspace permissions and access controls:

- **Workspace Membership**: Only workflows within the same workspace can be executed
- **Permission Inheritance**: Child workflows inherit the execution permissions of the parent workflow
- **API Key Access**: Child workflows have access to the same API keys and environment variables as the parent
- **User Context**: The execution maintains the original user context for audit and logging purposes

## Best Practices

- **Keep workflows focused**: Design child workflows to handle specific, well-defined tasks
- **Minimize nesting depth**: Avoid deeply nested workflow hierarchies for better maintainability
- **Handle errors gracefully**: Implement proper error handling for child workflow failures
- **Document dependencies**: Clearly document which workflows depend on others
- **Version control**: Consider versioning strategies for workflows that are used as components
- **Test independently**: Ensure child workflows can be tested and validated independently
- **Monitor performance**: Be aware that nested workflows can impact overall execution time

## Common Patterns

### Microservice Architecture
Break down complex business processes into smaller, focused workflows that can be developed and maintained independently.

### Reusable Components
Create library workflows for common operations like data validation, email sending, or API integrations that can be reused across multiple projects.

### Conditional Execution
Use workflow blocks within conditional logic to execute different business processes based on runtime conditions.

### Parallel Processing
Combine workflow blocks with parallel execution to run multiple child workflows simultaneously for improved performance.

<Callout type="tip">
When designing modular workflows, think of each workflow as a function with clear inputs, outputs, and a single responsibility.
</Callout>
Binary file added apps/docs/public/static/dark/workflow-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/public/static/light/workflow-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions apps/sim/app/api/workflows/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { and, eq } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console-logger'
import { db } from '@/db'
import { workflow, workspaceMember } from '@/db/schema'

const logger = createLogger('WorkflowDetailAPI')

export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const requestId = crypto.randomUUID().slice(0, 8)
const startTime = Date.now()

try {
// Get the session
const session = await getSession()
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthorized workflow access attempt`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const { id: workflowId } = await params

if (!workflowId) {
return NextResponse.json({ error: 'Workflow ID is required' }, { status: 400 })
}

// Fetch the workflow from database
const workflowData = await db
.select()
.from(workflow)
.where(eq(workflow.id, workflowId))
.then((rows) => rows[0])

if (!workflowData) {
logger.warn(`[${requestId}] Workflow ${workflowId} not found`)
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
}

// Check if user has access to this workflow
// User can access if they own it OR if it's in a workspace they're part of
const canAccess = workflowData.userId === session.user.id

if (!canAccess && workflowData.workspaceId) {
// Check workspace membership
const membership = await db
.select()
.from(workspaceMember)
.where(
and(
eq(workspaceMember.workspaceId, workflowData.workspaceId),
eq(workspaceMember.userId, session.user.id)
)
)
.then((rows) => rows[0])

if (membership) {
// User is a member of the workspace, allow access
const elapsed = Date.now() - startTime
logger.info(`[${requestId}] Workflow ${workflowId} fetched in ${elapsed}ms`)
return NextResponse.json({ data: workflowData }, { status: 200 })
}
} else if (canAccess) {
// User owns the workflow, allow access
const elapsed = Date.now() - startTime
logger.info(`[${requestId}] Workflow ${workflowId} fetched in ${elapsed}ms`)
return NextResponse.json({ data: workflowData }, { status: 200 })
}

logger.warn(
`[${requestId}] User ${session.user.id} attempted to access workflow ${workflowId} without permission`
)
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
} catch (error: any) {
Comment thread
icecrasher321 marked this conversation as resolved.
const elapsed = Date.now() - startTime
logger.error(`[${requestId}] Error fetching workflow after ${elapsed}ms:`, error)
return NextResponse.json({ error: 'Failed to fetch workflow' }, { status: 500 })
}
}
Loading