Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
219a065
v0.4.12: guardrails, mistral models, privacy policy updates (#1608)
waleedlatif1 Oct 12, 2025
7f82ed3
v0.4.13: bugfixes for dev containers, posthog redirect, helm updates
icecrasher321 Oct 13, 2025
fb0fa1f
v0.4.14: canvas speedup and copilot context window
Sg312 Oct 14, 2025
2bc8c7b
v0.4.15: helm chart updates, telegram tools, youtube tools, file uplo…
waleedlatif1 Oct 15, 2025
04f109c
v0.4.16: executions dashboard, UI fixes, zep tools, slack fixes
icecrasher321 Oct 16, 2025
da091df
v0.4.17: input format + files support for webhooks, docs updates, das…
waleedlatif1 Oct 16, 2025
e4ddeb0
v0.4.18: file upload tools, copilot upgrade, docs changes, model filt…
icecrasher321 Oct 19, 2025
641e353
v0.4.19: landing page fix
icecrasher321 Oct 19, 2025
9751c9f
v0.4.20: internal request, kb url fixes, docs styling
icecrasher321 Oct 21, 2025
1b7437a
v0.4.21: more internal auth changes, supabase vector search tool
icecrasher321 Oct 22, 2025
71ae27b
v0.4.22: fix execution context pass for google sheets
icecrasher321 Oct 22, 2025
9b2490c
v0.4.23: webflow tools + triggers, copilot api key fix (#1723)
waleedlatif1 Oct 23, 2025
7f1ff7f
fix(billing): should allow restoring subscription (#1728)
icecrasher321 Oct 25, 2025
a02016e
v0.4.24: sso for chat deployment, usage indicator for file storage, m…
icecrasher321 Oct 27, 2025
6d1b93e
improvement(start): revert to start block
icecrasher321 Oct 28, 2025
2673aed
make it work with start block
icecrasher321 Oct 28, 2025
ba374ec
fix start block persistence
icecrasher321 Oct 28, 2025
4590b5c
cleanup triggers
icecrasher321 Oct 28, 2025
815b0bc
debounce status checks
icecrasher321 Oct 28, 2025
66ed5ec
update docs
icecrasher321 Oct 29, 2025
9a4b9e2
v0.4.25: variables block, sort ordering for kb, careers page, storage…
waleedlatif1 Oct 29, 2025
3f2aff5
Merge branch 'main' into improvement/sim-294
icecrasher321 Oct 31, 2025
63e390c
Merge origin/staging into improvement/sim-294
icecrasher321 Oct 31, 2025
c4727e0
remove unused code
icecrasher321 Oct 31, 2025
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
29 changes: 9 additions & 20 deletions apps/docs/content/docs/en/triggers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,35 @@ import { Card, Cards } from 'fumadocs-ui/components/card'

## Core Triggers

Pick one trigger per workflow to define how it starts:
Use the Start block for everything originating from the editor, deploy-to-API, or deploy-to-chat experiences. Other triggers remain available for event-driven workflows:

<Cards>
<Card title="API" href="/triggers/api">
HTTP endpoint that maps JSON bodies into workflow inputs
<Card title="Start" href="/triggers/start">
Unified entry point that supports editor runs, API deployments and chat deployments
</Card>
<Card title="Chat" href="/triggers/chat">
Deployed chat interface with streaming responses
</Card>
<Card title="Input Form" href="/triggers/input-form">
Typed manual input used in editor runs and child workflows
</Card>
<Card title="Manual" href="/triggers/manual">
On-demand runs with no additional data
<Card title="Webhook" href="/triggers/webhook">
Receive external webhook payloads
</Card>
<Card title="Schedule" href="/triggers/schedule">
Cron or interval based execution
</Card>
<Card title="Webhook" href="/triggers/webhook">
Receive external webhook payloads
</Card>
</Cards>

## Quick Comparison

| Trigger | Start condition |
|---------|-----------------|
| **API** | Authenticated HTTP POST |
| **Chat** | Chat deployment message |
| **Input Form** | On manual submit in editor or parent workflow |
| **Manual** | Run button in editor |
| **Start** | Editor runs, deploy-to-API requests, or chat messages |
| **Schedule** | Timer managed in schedule modal |
| **Webhook** | On inbound HTTP request |

> The Start block always exposes `input`, `conversationId`, and `files` fields. Add custom fields to the input format for additional structured data.

## Using Triggers

1. Drop the trigger block in the start slot.
1. Drop the Start block in the start slot (or an alternate trigger like Webhook/Schedule).
2. Configure any required schema or auth.
3. Connect the block to the rest of the workflow.

> Deployments power every trigger. Update the workflow, redeploy, and all trigger entry points pick up the new snapshot. Learn more in [Execution → Deployment Snapshots](/execution).

Legacy Starter blocks remain for existing flows but no longer appear in new builds.
2 changes: 1 addition & 1 deletion apps/docs/content/docs/en/triggers/meta.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"pages": ["index", "api", "chat", "input-form", "manual", "schedule", "starter", "webhook"]
"pages": ["index", "start", "schedule", "webhook", "starter"]
}
90 changes: 90 additions & 0 deletions apps/docs/content/docs/en/triggers/start.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
title: Start
---

import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Image } from '@/components/ui/image'

The Start block is the default trigger for workflows built in Sim. It collects structured inputs and fans out to the rest of your graph for editor tests, API deployments, and chat experiences.

<div className="flex justify-center">
<Image
src="/static/start.png"
alt="Start block with Input Format fields"
width={360}
height={380}
className="my-6"
/>
</div>

<Callout type="info">
The Start block sits in the start slot when you create a workflow. Keep it there when you want the same entry point to serve editor runs, deploy-to-API requests, and chat sessions. Swap it with Webhook or Schedule triggers when you only need event-driven execution.
</Callout>

## Fields exposed by Start

The Start block emits different data depending on the execution surface:

- **Input Format fields** — Every field you add becomes available as `<start.fieldName>`. For example, a `customerId` field shows up as `<start.customerId>` in downstream blocks and templates.
- **Chat-only fields** — When the workflow runs from the chat side panel or a deployed chat experience, Sim also provides `<start.input>` (latest user message), `<start.conversationId>` (active session id), and `<start.files>` (chat attachments).

Keep Input Format fields scoped to the names you expect to reference later—those values are the only structured fields shared across editor, API, and chat runs.

## Configure the Input Format

Use the Input Format sub-block to define the schema that applies across execution modes:

1. Add a field for each value you want to collect.
2. Choose a type (`string`, `number`, `boolean`, `object`, `array`, or `files`). File fields accept uploads from chat and API callers.
3. Provide default values when you want the manual run modal to populate test data automatically. These defaults are ignored for deployed executions.
4. Reorder fields to control how they appear in the editor form.

Reference structured values downstream with expressions such as `<start.customerId>` depending on the block you connect.

## How it behaves per entry point

<Tabs items={['Editor run', 'Deploy to API', 'Deploy to chat']}>
<Tab>
<div className="space-y-3">
<p>
When you click <strong>Run</strong> in the editor, the Start block renders the Input Format as a form. Default values make it easy to retest without retyping data. Submitting the form triggers the workflow immediately and the values become available on <code>&lt;start.fieldName&gt;</code> (for example <code>&lt;start.sampleField&gt;</code>).
</p>
<p>
File fields in the form upload directly into the corresponding `<start.fieldName>`; use those values to feed downstream tools or storage steps.
</p>
</div>
</Tab>
<Tab>
<div className="space-y-3">
<p>
Deploying to API turns the Input Format into a JSON contract for clients. Each field becomes part of the request body, and Sim coerces primitive types on ingestion. File fields expect objects that reference uploaded files; use the execution file upload endpoint before invoking the workflow.
</p>
<p>
API callers can include additional optional properties. They are preserved inside `<start.fieldName>` outputs so you can experiment without redeploying immediately.
</p>
</div>
</Tab>
<Tab>
<div className="space-y-3">
<p>
In chat deployments the Start block binds to the active conversation. The latest message fills <code>&lt;start.input&gt;</code>, the session identifier is available at <code>&lt;start.conversationId&gt;</code>, and user attachments appear on <code>&lt;start.files&gt;</code>, alongside any Input Format fields scoped as <code>&lt;start.fieldName&gt;</code>.
</p>
<p>
If you launch chat with additional structured context (for example from an embed), it merges into the corresponding `<start.fieldName>` outputs, keeping downstream blocks consistent with API and manual runs.
</p>
</div>
</Tab>
</Tabs>

## Referencing Start data downstream

- Connect `<start.fieldName>` directly into agents, tools, or functions that expect structured payloads.
- Use templating syntax like `<start.sampleField>` or `<start.files[0].url>` (chat only) in prompt fields.
- Keep `<start.conversationId>` handy when you need to group outputs, update conversation history, or call back into the chat API.

## Best practices

- Treat the Start block as the single entry point when you want to support both API and chat callers.
- Prefer named Input Format fields over parsing raw JSON in downstream nodes; type coercion happens automatically.
- Add validation or routing immediately after Start if certain fields are required for your workflow to succeed.
67 changes: 0 additions & 67 deletions apps/docs/content/docs/en/triggers/starter.mdx

This file was deleted.

Binary file added apps/docs/public/static/start.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 removed apps/docs/public/static/starter.png
Binary file not shown.
19 changes: 12 additions & 7 deletions apps/sim/app/api/workflows/[id]/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
import { decryptSecret, generateRequestId } from '@/lib/utils'
import { loadDeployedWorkflowState } from '@/lib/workflows/db-helpers'
import { TriggerUtils } from '@/lib/workflows/triggers'
import { StartBlockPath, TriggerUtils } from '@/lib/workflows/triggers'
import {
createHttpResponseFromBlock,
updateWorkflowRunCounts,
Expand Down Expand Up @@ -317,10 +317,9 @@ export async function executeWorkflow(
throw new Error(errorMsg)
}

const startBlockId = startBlock.blockId
const triggerBlock = startBlock.block
const { blockId: startBlockId, block: triggerBlock, path: startPath } = startBlock

if (triggerBlock.type !== 'starter') {
if (startPath !== StartBlockPath.LEGACY_STARTER) {
const outgoingConnections = serializedWorkflow.connections.filter(
(conn) => conn.source === startBlockId
)
Expand Down Expand Up @@ -557,7 +556,7 @@ export async function POST(
: undefined),
workflowTriggerType:
body.workflowTriggerType || (isInternalCall && body.stream ? 'chat' : 'api'),
input: body.input !== undefined ? body.input : body,
input: body,
}
}

Expand Down Expand Up @@ -607,13 +606,19 @@ export async function POST(
const blocks = deployedData.blocks || {}
logger.info(`[${requestId}] Loaded ${Object.keys(blocks).length} blocks from workflow`)

const startTriggerBlock = Object.values(blocks).find(
(block: any) => block.type === 'start_trigger'
) as any
const apiTriggerBlock = Object.values(blocks).find(
(block: any) => block.type === 'api_trigger'
) as any
logger.info(`[${requestId}] Start trigger block found:`, !!startTriggerBlock)
logger.info(`[${requestId}] API trigger block found:`, !!apiTriggerBlock)

if (apiTriggerBlock?.subBlocks?.inputFormat?.value) {
const inputFormat = apiTriggerBlock.subBlocks.inputFormat.value as Array<{
const triggerBlock = startTriggerBlock || apiTriggerBlock

if (triggerBlock?.subBlocks?.inputFormat?.value) {
const inputFormat = triggerBlock.subBlocks.inputFormat.value as Array<{
name: string
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'files'
}>
Expand Down
9 changes: 8 additions & 1 deletion apps/sim/app/api/workflows/[id]/status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
const normalizedData = await loadWorkflowFromNormalizedTables(id)

if (!normalizedData) {
return createErrorResponse('Failed to load workflow state', 500)
// Workflow exists but has no blocks in normalized tables (empty workflow or not migrated)
// This is valid state - return success with no redeployment needed
return createSuccessResponse({
isDeployed: validation.workflow.isDeployed,
deployedAt: validation.workflow.deployedAt,
isPublished: validation.workflow.isPublished,
needsRedeployment: false,
})
}

const currentState = {
Expand Down
9 changes: 9 additions & 0 deletions apps/sim/app/api/workspaces/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { and, desc, eq, isNull } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'
import { saveWorkflowToNormalizedTables } from '@/lib/workflows/db-helpers'
import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults'

const logger = createLogger('Workspaces')

Expand Down Expand Up @@ -136,6 +138,13 @@ async function createWorkspace(userId: string, name: string) {
`Created workspace ${workspaceId} with initial workflow ${workflowId} for user ${userId}`
)
})

const { workflowState } = buildDefaultWorkflowArtifacts()
const seedResult = await saveWorkflowToNormalizedTables(workflowId, workflowState)

if (!seedResult.success) {
throw new Error(seedResult.error || 'Failed to seed default workflow state')
}
} catch (error) {
logger.error(`Failed to create workspace ${workspaceId} with initial workflow:`, error)
throw error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getEnv } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/db-helpers'
import { resolveStartCandidates, StartBlockPath } from '@/lib/workflows/triggers'
import { DeploymentInfo } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components'
import { ChatDeploy } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy'
import { DeployedWorkflowModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deployment-controls/components/deployed-workflow-modal'
Expand Down Expand Up @@ -114,13 +115,17 @@ export function DeployModal({
let inputFormatExample = ''
try {
const blocks = Object.values(useWorkflowStore.getState().blocks)
const candidates = resolveStartCandidates(useWorkflowStore.getState().blocks, {
execution: 'api',
})

// Check for API trigger block first (takes precedence)
const apiTriggerBlock = blocks.find((block) => block.type === 'api_trigger')
// Fall back to legacy starter block
const starterBlock = blocks.find((block) => block.type === 'starter')
const targetCandidate =
candidates.find((candidate) => candidate.path === StartBlockPath.UNIFIED) ||
candidates.find((candidate) => candidate.path === StartBlockPath.SPLIT_API) ||
candidates.find((candidate) => candidate.path === StartBlockPath.SPLIT_INPUT) ||
candidates.find((candidate) => candidate.path === StartBlockPath.LEGACY_STARTER)

const targetBlock = apiTriggerBlock || starterBlock
const targetBlock = targetCandidate?.block

if (targetBlock) {
const inputFormat = useSubBlockStore.getState().getValue(targetBlock.id, 'inputFormat')
Expand Down
Loading