Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
bea0a68
improvement(collab): do not refetch active workflow id
icecrasher321 Feb 1, 2026
1da3407
progress on files
icecrasher321 Feb 1, 2026
39ca1f6
more integrations
icecrasher321 Feb 2, 2026
9ec0c8f
separate server and client logic
icecrasher321 Feb 2, 2026
f4a3c94
consolidate more code
icecrasher321 Feb 2, 2026
5a0becf
fix integrations
icecrasher321 Feb 3, 2026
42767fc
fix types
icecrasher321 Feb 3, 2026
5ecbf6c
consolidate more code
icecrasher321 Feb 3, 2026
a65f3b8
fix tests
icecrasher321 Feb 3, 2026
3ceabbb
fix more bugbot comments
icecrasher321 Feb 3, 2026
1ff3540
fix type check
icecrasher321 Feb 3, 2026
7570e50
Merge branch 'staging' into improvement/double-fetch
icecrasher321 Feb 3, 2026
1c857cd
fix circular impport
icecrasher321 Feb 3, 2026
6e642fc
address more bugbot comments
icecrasher321 Feb 3, 2026
cbe0f8a
fix ocr integrations
icecrasher321 Feb 3, 2026
a6ec6a0
fix typing
icecrasher321 Feb 3, 2026
0aeaf6f
remove leftover type
icecrasher321 Feb 3, 2026
4169a25
address bugbot comment
icecrasher321 Feb 3, 2026
66b954d
fix file block adv mode
icecrasher321 Feb 3, 2026
6e5e8de
fix
icecrasher321 Feb 3, 2026
c230e1a
normalize file input
icecrasher321 Feb 3, 2026
2854906
fix v2 blocmks for ocr
icecrasher321 Feb 3, 2026
3b74708
fix for v2 versions
icecrasher321 Feb 3, 2026
ff0753a
fix more v2 blocks
icecrasher321 Feb 3, 2026
47c9604
update single file blocks
icecrasher321 Feb 3, 2026
f256a9f
make interface simpler
icecrasher321 Feb 3, 2026
fa81609
cleanup fireflies
icecrasher321 Feb 3, 2026
dc3d449
remove file only annotation
icecrasher321 Feb 3, 2026
4f2b5a5
accept all types
icecrasher321 Feb 3, 2026
a529f06
added wand to ssh block
waleedlatif1 Feb 3, 2026
ed1ca6e
user files should be passed through
icecrasher321 Feb 3, 2026
4669ec9
Merge branch 'improvement/double-fetch' of github.com:simstudioai/sim…
icecrasher321 Feb 3, 2026
b0457bc
improve docs
icecrasher321 Feb 3, 2026
cfc3604
fix slack to include successful execs
icecrasher321 Feb 3, 2026
bd5866e
fix dropbox upload file
icecrasher321 Feb 3, 2026
2d96ac5
fix sendgrid
icecrasher321 Feb 3, 2026
aa1b158
fix dropbox
icecrasher321 Feb 3, 2026
5a78b55
fix
icecrasher321 Feb 3, 2026
b7ccbc8
fix
icecrasher321 Feb 3, 2026
1282bef
update skills
icecrasher321 Feb 4, 2026
6b95b62
fix uploaded file
icecrasher321 Feb 4, 2026
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
134 changes: 134 additions & 0 deletions apps/docs/content/docs/en/execution/files.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
title: Passing Files
---

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

Sim makes it easy to work with files throughout your workflows. Blocks can receive files, process them, and pass them to other blocks seamlessly.

## File Objects

When blocks output files (like Gmail attachments, generated images, or parsed documents), they return a standardized file object:

```json
{
"name": "report.pdf",
"url": "https://...",
"base64": "JVBERi0xLjQK...",
"type": "application/pdf",
"size": 245678
}
```

You can access any of these properties when referencing files from previous blocks.

## Passing Files Between Blocks

Reference files from previous blocks using the tag dropdown. Click in any file input field and type `<` to see available outputs.

**Common patterns:**

```
// Single file from a block
<gmail.attachments[0]>

// Pass the whole file object
<file_parser.files[0]>

// Access specific properties
<gmail.attachments[0].name>
<gmail.attachments[0].base64>
```

Most blocks accept the full file object and extract what they need automatically. You don't need to manually extract `base64` or `url` in most cases.

## Triggering Workflows with Files

When calling a workflow via API that expects file input, include files in your request:

<Tabs items={['Base64', 'URL']}>
<Tab value="Base64">
```bash
curl -X POST "https://sim.ai/api/workflows/YOUR_WORKFLOW_ID/execute" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"document": {
"name": "report.pdf",
"base64": "JVBERi0xLjQK...",
"type": "application/pdf"
}
}'
```
</Tab>
<Tab value="URL">
```bash
curl -X POST "https://sim.ai/api/workflows/YOUR_WORKFLOW_ID/execute" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"document": {
"name": "report.pdf",
"url": "https://example.com/report.pdf",
"type": "application/pdf"
}
}'
```
</Tab>
</Tabs>

The workflow's Start block should have an input field configured to receive the file parameter.

## Receiving Files in API Responses

When a workflow outputs files, they're included in the response:

```json
{
"success": true,
"output": {
"generatedFile": {
"name": "output.png",
"url": "https://...",
"base64": "iVBORw0KGgo...",
"type": "image/png",
"size": 34567
}
}
}
```

Use `url` for direct downloads or `base64` for inline processing.

## Blocks That Work with Files

**File inputs:**
- **File** - Parse documents, images, and text files
- **Vision** - Analyze images with AI models
- **Mistral Parser** - Extract text from PDFs

**File outputs:**
- **Gmail** - Email attachments
- **Slack** - Downloaded files
- **TTS** - Generated audio files
- **Video Generator** - Generated videos
- **Image Generator** - Generated images

**File storage:**
- **Supabase** - Upload/download from storage
- **S3** - AWS S3 operations
- **Google Drive** - Drive file operations
- **Dropbox** - Dropbox file operations

<Callout type="info">
Files are automatically available to downstream blocks. The execution engine handles all file transfer and format conversion.
</Callout>

## Best Practices

1. **Use file objects directly** - Pass the full file object rather than extracting individual properties. Blocks handle the conversion automatically.

2. **Check file types** - Ensure the file type matches what the receiving block expects. The Vision block needs images, the File block handles documents.

3. **Consider file size** - Large files increase execution time. For very large files, consider using storage blocks (S3, Supabase) for intermediate storage.
2 changes: 1 addition & 1 deletion apps/docs/content/docs/en/execution/meta.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"pages": ["index", "basics", "api", "logging", "costs"]
"pages": ["index", "basics", "files", "api", "logging", "costs"]
}
4 changes: 2 additions & 2 deletions apps/sim/app/api/a2a/serve/[agentId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { getBrandConfig } from '@/lib/branding/branding'
import { acquireLock, getRedisClient, releaseLock } from '@/lib/core/config/redis'
import { validateExternalUrl } from '@/lib/core/security/input-validation'
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
import { SSE_HEADERS } from '@/lib/core/utils/sse'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { markExecutionCancelled } from '@/lib/execution/cancellation'
Expand Down Expand Up @@ -1119,7 +1119,7 @@ async function handlePushNotificationSet(
)
}

const urlValidation = validateExternalUrl(
const urlValidation = await validateUrlWithDNS(
params.pushNotificationConfig.url,
'Push notification URL'
)
Expand Down
13 changes: 9 additions & 4 deletions apps/sim/app/api/files/parse/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { createLogger } from '@sim/logger'
import binaryExtensionsList from 'binary-extensions'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { secureFetchWithPinnedIP, validateUrlWithDNS } from '@/lib/core/security/input-validation'
import {
secureFetchWithPinnedIP,
validateUrlWithDNS,
} from '@/lib/core/security/input-validation.server'
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
import { isSupportedFileType, parseFile } from '@/lib/file-parsers'
import { isUsingCloudStorage, type StorageContext, StorageService } from '@/lib/uploads'
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
Expand All @@ -19,6 +23,7 @@ import {
getMimeTypeFromExtension,
getViewerUrl,
inferContextFromKey,
isInternalFileUrl,
} from '@/lib/uploads/utils/file-utils'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
import { verifyFileAccess } from '@/app/api/files/authorization'
Expand Down Expand Up @@ -215,7 +220,7 @@ async function parseFileSingle(
}
}

if (filePath.includes('/api/files/serve/')) {
if (isInternalFileUrl(filePath)) {
return handleCloudFile(filePath, fileType, undefined, userId, executionContext)
}

Expand Down Expand Up @@ -246,7 +251,7 @@ function validateFilePath(filePath: string): { isValid: boolean; error?: string
return { isValid: false, error: 'Invalid path: tilde character not allowed' }
}

if (filePath.startsWith('/') && !filePath.startsWith('/api/files/serve/')) {
if (filePath.startsWith('/') && !isInternalFileUrl(filePath)) {
return { isValid: false, error: 'Path outside allowed directory' }
}

Expand Down Expand Up @@ -420,7 +425,7 @@ async function handleExternalUrl(

return parseResult
} catch (error) {
logger.error(`Error handling external URL ${url}:`, error)
logger.error(`Error handling external URL ${sanitizeUrlForLog(url)}:`, error)
return {
success: false,
error: `Error fetching URL: ${(error as Error).message}`,
Expand Down
9 changes: 9 additions & 0 deletions apps/sim/app/api/tools/a2a/send-message/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createA2AClient, extractTextContent, isTerminalState } from '@/lib/a2a/utils'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
import { generateRequestId } from '@/lib/core/utils/request'

export const dynamic = 'force-dynamic'
Expand Down Expand Up @@ -95,6 +96,14 @@ export async function POST(request: NextRequest) {
if (validatedData.files && validatedData.files.length > 0) {
for (const file of validatedData.files) {
if (file.type === 'url') {
const urlValidation = await validateUrlWithDNS(file.data, 'fileUrl')
if (!urlValidation.isValid) {
return NextResponse.json(
{ success: false, error: urlValidation.error },
{ status: 400 }
)
}

const filePart: FilePart = {
kind: 'file',
file: {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/tools/a2a/set-push-notification/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createA2AClient } from '@/lib/a2a/utils'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateExternalUrl } from '@/lib/core/security/input-validation'
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
import { generateRequestId } from '@/lib/core/utils/request'

export const dynamic = 'force-dynamic'
Expand Down Expand Up @@ -40,7 +40,7 @@ export async function POST(request: NextRequest) {
const body = await request.json()
const validatedData = A2ASetPushNotificationSchema.parse(body)

const urlValidation = validateExternalUrl(validatedData.webhookUrl, 'Webhook URL')
const urlValidation = await validateUrlWithDNS(validatedData.webhookUrl, 'Webhook URL')
if (!urlValidation.isValid) {
logger.warn(`[${requestId}] Invalid webhook URL`, { error: urlValidation.error })
return NextResponse.json(
Expand Down
3 changes: 3 additions & 0 deletions apps/sim/app/api/tools/confluence/upload-attachment/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export async function POST(request: NextRequest) {
formData.append('comment', comment)
}

// Add minorEdit field as required by Confluence API
formData.append('minorEdit', 'false')

const response = await fetch(url, {
method: 'POST',
headers: {
Expand Down
16 changes: 15 additions & 1 deletion apps/sim/app/api/tools/discord/send-message/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { validateNumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { RawFileInputArraySchema } from '@/lib/uploads/utils/file-schemas'
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'

Expand All @@ -15,7 +16,7 @@ const DiscordSendMessageSchema = z.object({
botToken: z.string().min(1, 'Bot token is required'),
channelId: z.string().min(1, 'Channel ID is required'),
content: z.string().optional().nullable(),
files: z.array(z.any()).optional().nullable(),
files: RawFileInputArraySchema.optional().nullable(),
})

export async function POST(request: NextRequest) {
Expand Down Expand Up @@ -101,6 +102,12 @@ export async function POST(request: NextRequest) {
logger.info(`[${requestId}] Processing ${validatedData.files.length} file(s)`)

const userFiles = processFilesToUserFiles(validatedData.files, requestId, logger)
const filesOutput: Array<{
name: string
mimeType: string
data: string
size: number
}> = []

if (userFiles.length === 0) {
logger.warn(`[${requestId}] No valid files to upload, falling back to text-only`)
Expand Down Expand Up @@ -137,6 +144,12 @@ export async function POST(request: NextRequest) {
logger.info(`[${requestId}] Downloading file ${i}: ${userFile.name}`)

const buffer = await downloadFileFromStorage(userFile, requestId, logger)
filesOutput.push({
name: userFile.name,
mimeType: userFile.type || 'application/octet-stream',
data: buffer.toString('base64'),
size: buffer.length,
})

const blob = new Blob([new Uint8Array(buffer)], { type: userFile.type })
formData.append(`files[${i}]`, blob, userFile.name)
Expand Down Expand Up @@ -173,6 +186,7 @@ export async function POST(request: NextRequest) {
message: data.content,
data: data,
fileCount: userFiles.length,
files: filesOutput,
},
})
} catch (error) {
Expand Down
Loading