diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx
index 39ad880d9..b46467aa3 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx
@@ -34,6 +34,103 @@ interface UploadedFile {
type: string
}
+interface SingleFileSelectorProps {
+ file: UploadedFile
+ options: Array<{ label: string; value: string; disabled?: boolean }>
+ selectedValue: string
+ inputValue: string
+ onInputChange: (value: string) => void
+ onClear: (e: React.MouseEvent) => void
+ onOpenChange: (open: boolean) => void
+ disabled: boolean
+ isLoading: boolean
+ formatFileSize: (bytes: number) => string
+ truncateMiddle: (text: string, start?: number, end?: number) => string
+ isDeleting: boolean
+}
+
+/**
+ * Single file selector component that shows the selected file with both
+ * a clear button (X) and a chevron to change the selection.
+ * Follows the same pattern as SelectorCombobox for consistency.
+ */
+function SingleFileSelector({
+ file,
+ options,
+ selectedValue,
+ inputValue,
+ onInputChange,
+ onClear,
+ onOpenChange,
+ disabled,
+ isLoading,
+ formatFileSize,
+ truncateMiddle,
+ isDeleting,
+}: SingleFileSelectorProps) {
+ const displayLabel = `${truncateMiddle(file.name, 20, 12)} (${formatFileSize(file.size)})`
+ const [localInputValue, setLocalInputValue] = useState(displayLabel)
+ const [isEditing, setIsEditing] = useState(false)
+
+ // Sync display label when file changes
+ useEffect(() => {
+ if (!isEditing) {
+ setLocalInputValue(displayLabel)
+ }
+ }, [displayLabel, isEditing])
+
+ return (
+
+
{
+ // Check if user selected an option
+ const matched = options.find((opt) => opt.value === newValue || opt.label === newValue)
+ if (matched) {
+ setIsEditing(false)
+ setLocalInputValue(displayLabel)
+ onInputChange(matched.value)
+ return
+ }
+ // User is typing to search
+ setIsEditing(true)
+ setLocalInputValue(newValue)
+ }}
+ onOpenChange={(open) => {
+ if (!open) {
+ setIsEditing(false)
+ setLocalInputValue(displayLabel)
+ }
+ onOpenChange(open)
+ }}
+ placeholder={isLoading ? 'Loading files...' : 'Select or upload file'}
+ disabled={disabled || isDeleting}
+ editable={true}
+ filterOptions={isEditing}
+ isLoading={isLoading}
+ inputProps={{
+ className: 'pr-[60px]',
+ }}
+ />
+
+ {isDeleting ? (
+
+ ) : (
+
+ )}
+
+
+ )
+}
+
interface UploadingFile {
id: string
name: string
@@ -500,6 +597,7 @@ export function FileUpload({
const hasFiles = filesArray.length > 0
const isUploading = uploadingFiles.length > 0
+ // Options for multiple file mode (filters out already selected files)
const comboboxOptions = useMemo(
() => [
{ label: 'Upload New File', value: '__upload_new__' },
@@ -516,10 +614,43 @@ export function FileUpload({
[availableWorkspaceFiles, acceptedTypes]
)
+ // Options for single file mode (includes all files, selected one will be highlighted)
+ const singleFileOptions = useMemo(
+ () => [
+ { label: 'Upload New File', value: '__upload_new__' },
+ ...workspaceFiles.map((file) => {
+ const isAccepted =
+ !acceptedTypes || acceptedTypes === '*' || isFileTypeAccepted(file.type, acceptedTypes)
+ return {
+ label: file.name,
+ value: file.id,
+ disabled: !isAccepted,
+ }
+ }),
+ ],
+ [workspaceFiles, acceptedTypes]
+ )
+
+ // Find the selected file's workspace ID for highlighting in single file mode
+ const selectedFileId = useMemo(() => {
+ if (!hasFiles || multiple) return ''
+ const currentFile = filesArray[0]
+ if (!currentFile) return ''
+ // Match by key or path
+ const matchedWorkspaceFile = workspaceFiles.find(
+ (wf) =>
+ wf.key === currentFile.key ||
+ wf.name === currentFile.name ||
+ currentFile.path?.includes(wf.key)
+ )
+ return matchedWorkspaceFile?.id || ''
+ }, [filesArray, workspaceFiles, hasFiles, multiple])
+
const handleComboboxChange = (value: string) => {
setInputValue(value)
- const selectedFile = availableWorkspaceFiles.find((file) => file.id === value)
+ // Look in full workspaceFiles list (not filtered) to allow re-selecting same file in single mode
+ const selectedFile = workspaceFiles.find((file) => file.id === value)
const isAcceptedType =
selectedFile &&
(!acceptedTypes ||
@@ -559,16 +690,17 @@ export function FileUpload({
{/* Error message */}
{uploadError && {uploadError}
}
- {/* File list with consistent spacing */}
- {(hasFiles || isUploading) && (
+ {/* File list with consistent spacing - only show for multiple mode or when uploading */}
+ {((hasFiles && multiple) || isUploading) && (
- {/* Only show files that aren't currently uploading */}
- {filesArray.map((file) => {
- const isCurrentlyUploading = uploadingFiles.some(
- (uploadingFile) => uploadingFile.name === file.name
- )
- return !isCurrentlyUploading && renderFileItem(file)
- })}
+ {/* Only show files that aren't currently uploading (for multiple mode only) */}
+ {multiple &&
+ filesArray.map((file) => {
+ const isCurrentlyUploading = uploadingFiles.some(
+ (uploadingFile) => uploadingFile.name === file.name
+ )
+ return !isCurrentlyUploading && renderFileItem(file)
+ })}
{isUploading && (
<>
{uploadingFiles.map(renderUploadingItem)}
@@ -604,6 +736,26 @@ export function FileUpload({
/>
)}
+ {/* Single file mode with file selected: show combobox-style UI with X and chevron */}
+ {hasFiles && !multiple && !isUploading && (
+
handleRemoveFile(filesArray[0], e)}
+ onOpenChange={(open) => {
+ if (open) void loadWorkspaceFiles()
+ }}
+ disabled={disabled}
+ isLoading={loadingWorkspaceFiles}
+ formatFileSize={formatFileSize}
+ truncateMiddle={truncateMiddle}
+ isDeleting={deletingFiles[filesArray[0]?.path || '']}
+ />
+ )}
+
{/* Show dropdown selector if no files and not uploading */}
{!hasFiles && !isUploading && (
{
+ e.preventDefault()
+ e.stopPropagation()
+ if (readOnly || disabled) return
+ setStoreValue(null)
+ setInputValue('')
+ onOptionChange?.('')
+ },
+ [setStoreValue, onOptionChange, readOnly, disabled]
+ )
+
+ const showClearButton = Boolean(activeValue) && !disabled && !readOnly
+
return (
{({ ref, onDrop, onDragOver }) => (
- {
- const matched = optionMap.get(newValue)
- if (matched) {
- setInputValue(matched.label)
- setIsEditing(false)
- handleSelection(matched.id)
- return
- }
- if (allowSearch) {
- setInputValue(newValue)
- setIsEditing(true)
- setSearchTerm(newValue)
- }
- }}
- placeholder={placeholder || subBlock.placeholder || 'Select an option'}
- disabled={disabled || readOnly}
- editable={allowSearch}
- filterOptions={allowSearch}
- inputRef={ref as React.RefObject}
- inputProps={{
- onDrop: onDrop as (e: React.DragEvent) => void,
- onDragOver: onDragOver as (e: React.DragEvent) => void,
- }}
- isLoading={isLoading}
- error={error instanceof Error ? error.message : null}
- />
+
+ {
+ const matched = optionMap.get(newValue)
+ if (matched) {
+ setInputValue(matched.label)
+ setIsEditing(false)
+ handleSelection(matched.id)
+ return
+ }
+ if (allowSearch) {
+ setInputValue(newValue)
+ setIsEditing(true)
+ setSearchTerm(newValue)
+ }
+ }}
+ placeholder={placeholder || subBlock.placeholder || 'Select an option'}
+ disabled={disabled || readOnly}
+ editable={allowSearch}
+ filterOptions={allowSearch}
+ inputRef={ref as React.RefObject}
+ inputProps={{
+ onDrop: onDrop as (e: React.DragEvent) => void,
+ onDragOver: onDragOver as (e: React.DragEvent) => void,
+ className: showClearButton ? 'pr-[60px]' : undefined,
+ }}
+ isLoading={isLoading}
+ error={error instanceof Error ? error.message : null}
+ />
+ {showClearButton && (
+
+
+
+ )}
+
)}
diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts
index d14168d5a..aebca65dc 100644
--- a/apps/sim/blocks/blocks/google_drive.ts
+++ b/apps/sim/blocks/blocks/google_drive.ts
@@ -121,10 +121,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr
required: false,
},
{
- id: 'folderSelector',
+ id: 'uploadFolderSelector',
title: 'Select Parent Folder',
type: 'file-selector',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'uploadFolderId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -137,10 +137,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr
condition: { field: 'operation', value: ['create_file', 'upload'] },
},
{
- id: 'manualFolderId',
+ id: 'uploadManualFolderId',
title: 'Parent Folder ID',
type: 'short-input',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'uploadFolderId',
placeholder: 'Enter parent folder ID (leave empty for root folder)',
mode: 'advanced',
condition: { field: 'operation', value: ['create_file', 'upload'] },
@@ -193,10 +193,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr
required: true,
},
{
- id: 'folderSelector',
+ id: 'createFolderParentSelector',
title: 'Select Parent Folder',
type: 'file-selector',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'createFolderParentId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -210,20 +210,20 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr
},
// Manual Folder ID input (advanced mode)
{
- id: 'manualFolderId',
+ id: 'createFolderManualParentId',
title: 'Parent Folder ID',
type: 'short-input',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'createFolderParentId',
placeholder: 'Enter parent folder ID (leave empty for root folder)',
mode: 'advanced',
condition: { field: 'operation', value: 'create_folder' },
},
// List Fields - Folder Selector (basic mode)
{
- id: 'folderSelector',
+ id: 'listFolderSelector',
title: 'Select Folder',
type: 'file-selector',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'listFolderId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -237,10 +237,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr
},
// Manual Folder ID input (advanced mode)
{
- id: 'manualFolderId',
+ id: 'listManualFolderId',
title: 'Folder ID',
type: 'short-input',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'listFolderId',
placeholder: 'Enter folder ID (leave empty for root folder)',
mode: 'advanced',
condition: { field: 'operation', value: 'list' },
@@ -279,10 +279,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
},
// Download File Fields - File Selector (basic mode)
{
- id: 'fileSelector',
+ id: 'downloadFileSelector',
title: 'Select File',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'downloadFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -292,13 +292,14 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'download' },
+ required: true,
},
// Manual File ID input (advanced mode)
{
- id: 'manualFileId',
+ id: 'downloadManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'downloadFileId',
placeholder: 'Enter file ID',
mode: 'advanced',
condition: { field: 'operation', value: 'download' },
@@ -339,10 +340,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
},
// Get File Info Fields
{
- id: 'fileSelector',
+ id: 'getFileSelector',
title: 'Select File',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'getFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -352,12 +353,13 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'get_file' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'getManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'getFileId',
placeholder: 'Enter file ID',
mode: 'advanced',
condition: { field: 'operation', value: 'get_file' },
@@ -365,10 +367,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
},
// Copy File Fields
{
- id: 'fileSelector',
+ id: 'copyFileSelector',
title: 'Select File to Copy',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'copyFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -378,12 +380,13 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'copy' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'copyManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'copyFileId',
placeholder: 'Enter file ID to copy',
mode: 'advanced',
condition: { field: 'operation', value: 'copy' },
@@ -397,10 +400,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
condition: { field: 'operation', value: 'copy' },
},
{
- id: 'folderSelector',
+ id: 'copyDestFolderSelector',
title: 'Destination Folder',
type: 'file-selector',
- canonicalParamId: 'destinationFolderId',
+ canonicalParamId: 'copyDestFolderId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -413,20 +416,20 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
condition: { field: 'operation', value: 'copy' },
},
{
- id: 'manualDestinationFolderId',
+ id: 'copyManualDestFolderId',
title: 'Destination Folder ID',
type: 'short-input',
- canonicalParamId: 'destinationFolderId',
+ canonicalParamId: 'copyDestFolderId',
placeholder: 'Enter destination folder ID (optional)',
mode: 'advanced',
condition: { field: 'operation', value: 'copy' },
},
// Update File Fields
{
- id: 'fileSelector',
+ id: 'updateFileSelector',
title: 'Select File to Update',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'updateFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -436,12 +439,13 @@ Return ONLY the query string - no explanations, no quotes around the whole thing
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'update' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'updateManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'updateFileId',
placeholder: 'Enter file ID to update',
mode: 'advanced',
condition: { field: 'operation', value: 'update' },
@@ -500,10 +504,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
},
// Trash File Fields
{
- id: 'fileSelector',
+ id: 'trashFileSelector',
title: 'Select File to Trash',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'trashFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -513,12 +517,13 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'trash' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'trashManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'trashFileId',
placeholder: 'Enter file ID to trash',
mode: 'advanced',
condition: { field: 'operation', value: 'trash' },
@@ -526,10 +531,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
},
// Delete File Fields
{
- id: 'fileSelector',
+ id: 'deleteFileSelector',
title: 'Select File to Delete',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'deleteFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -539,12 +544,13 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'delete' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'deleteManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'deleteFileId',
placeholder: 'Enter file ID to permanently delete',
mode: 'advanced',
condition: { field: 'operation', value: 'delete' },
@@ -552,10 +558,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
},
// Share File Fields
{
- id: 'fileSelector',
+ id: 'shareFileSelector',
title: 'Select File to Share',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'shareFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -565,12 +571,13 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'share' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'shareManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'shareFileId',
placeholder: 'Enter file ID to share',
mode: 'advanced',
condition: { field: 'operation', value: 'share' },
@@ -665,10 +672,10 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
},
// Unshare (Remove Permission) Fields
{
- id: 'fileSelector',
+ id: 'unshareFileSelector',
title: 'Select File',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'unshareFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -678,12 +685,13 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'unshare' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'unshareManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'unshareFileId',
placeholder: 'Enter file ID',
mode: 'advanced',
condition: { field: 'operation', value: 'unshare' },
@@ -699,10 +707,10 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
},
// List Permissions Fields
{
- id: 'fileSelector',
+ id: 'listPermissionsFileSelector',
title: 'Select File',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'listPermissionsFileId',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -712,12 +720,13 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'list_permissions' },
+ required: true,
},
{
- id: 'manualFileId',
+ id: 'listPermissionsManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'listPermissionsFileId',
placeholder: 'Enter file ID',
mode: 'advanced',
condition: { field: 'operation', value: 'list_permissions' },
@@ -778,11 +787,22 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
params: (params) => {
const {
credential,
- folderSelector,
- manualFolderId,
- manualDestinationFolderId,
- fileSelector,
- manualFileId,
+ // Folder canonical params (per-operation)
+ uploadFolderId,
+ createFolderParentId,
+ listFolderId,
+ copyDestFolderId,
+ // File canonical params (per-operation)
+ downloadFileId,
+ getFileId,
+ copyFileId,
+ updateFileId,
+ trashFileId,
+ deleteFileId,
+ shareFileId,
+ unshareFileId,
+ listPermissionsFileId,
+ // File upload
file,
fileUpload,
mimeType,
@@ -795,17 +815,56 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
// Normalize file input - handles both basic (file-upload) and advanced (short-input) modes
const normalizedFile = normalizeFileInput(file ?? fileUpload, { single: true })
- // Use folderSelector if provided, otherwise use manualFolderId
- const effectiveFolderId = (folderSelector || manualFolderId || '').trim()
+ // Resolve folderId based on operation
+ let effectiveFolderId: string | undefined
+ switch (params.operation) {
+ case 'create_file':
+ case 'upload':
+ effectiveFolderId = uploadFolderId?.trim() || undefined
+ break
+ case 'create_folder':
+ effectiveFolderId = createFolderParentId?.trim() || undefined
+ break
+ case 'list':
+ effectiveFolderId = listFolderId?.trim() || undefined
+ break
+ }
- // Use fileSelector if provided, otherwise use manualFileId
- const effectiveFileId = (fileSelector || manualFileId || '').trim()
+ // Resolve fileId based on operation
+ let effectiveFileId: string | undefined
+ switch (params.operation) {
+ case 'download':
+ effectiveFileId = downloadFileId?.trim() || undefined
+ break
+ case 'get_file':
+ effectiveFileId = getFileId?.trim() || undefined
+ break
+ case 'copy':
+ effectiveFileId = copyFileId?.trim() || undefined
+ break
+ case 'update':
+ effectiveFileId = updateFileId?.trim() || undefined
+ break
+ case 'trash':
+ effectiveFileId = trashFileId?.trim() || undefined
+ break
+ case 'delete':
+ effectiveFileId = deleteFileId?.trim() || undefined
+ break
+ case 'share':
+ effectiveFileId = shareFileId?.trim() || undefined
+ break
+ case 'unshare':
+ effectiveFileId = unshareFileId?.trim() || undefined
+ break
+ case 'list_permissions':
+ effectiveFileId = listPermissionsFileId?.trim() || undefined
+ break
+ }
- // Use folderSelector for destination or manualDestinationFolderId for copy operation
+ // Resolve destinationFolderId for copy operation
const effectiveDestinationFolderId =
- params.operation === 'copy'
- ? (folderSelector || manualDestinationFolderId || '').trim()
- : undefined
+ params.operation === 'copy' ? copyDestFolderId?.trim() || undefined : undefined
// Convert starred dropdown to boolean
const starredValue = starred === 'true' ? true : starred === 'false' ? false : undefined
@@ -816,9 +875,9 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
return {
credential,
- folderId: effectiveFolderId || undefined,
- fileId: effectiveFileId || undefined,
- destinationFolderId: effectiveDestinationFolderId || undefined,
+ folderId: effectiveFolderId,
+ fileId: effectiveFileId,
+ destinationFolderId: effectiveDestinationFolderId,
file: normalizedFile,
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
mimeType: mimeType,
@@ -834,13 +893,21 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Google Drive access token' },
- // File selection inputs
- fileSelector: { type: 'string', description: 'Selected file' },
- manualFileId: { type: 'string', description: 'Manual file identifier' },
- // Folder selection inputs
- folderSelector: { type: 'string', description: 'Selected folder' },
- manualFolderId: { type: 'string', description: 'Manual folder identifier' },
- manualDestinationFolderId: { type: 'string', description: 'Destination folder for copy' },
+ // Folder canonical params (per-operation)
+ uploadFolderId: { type: 'string', description: 'Parent folder for upload/create' },
+ createFolderParentId: { type: 'string', description: 'Parent folder for create folder' },
+ listFolderId: { type: 'string', description: 'Folder to list files from' },
+ copyDestFolderId: { type: 'string', description: 'Destination folder for copy' },
+ // File canonical params (per-operation)
+ downloadFileId: { type: 'string', description: 'File to download' },
+ getFileId: { type: 'string', description: 'File to get info for' },
+ copyFileId: { type: 'string', description: 'File to copy' },
+ updateFileId: { type: 'string', description: 'File to update' },
+ trashFileId: { type: 'string', description: 'File to trash' },
+ deleteFileId: { type: 'string', description: 'File to delete' },
+ shareFileId: { type: 'string', description: 'File to share' },
+ unshareFileId: { type: 'string', description: 'File to unshare' },
+ listPermissionsFileId: { type: 'string', description: 'File to list permissions for' },
// Upload and Create inputs
fileName: { type: 'string', description: 'File or folder name' },
file: { type: 'json', description: 'File to upload (UserFile object)' },
diff --git a/apps/sim/blocks/blocks/onedrive.ts b/apps/sim/blocks/blocks/onedrive.ts
index e2e3545fb..58c183553 100644
--- a/apps/sim/blocks/blocks/onedrive.ts
+++ b/apps/sim/blocks/blocks/onedrive.ts
@@ -140,10 +140,10 @@ export const OneDriveBlock: BlockConfig = {
},
{
- id: 'folderSelector',
+ id: 'uploadFolderSelector',
title: 'Select Parent Folder',
type: 'file-selector',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'uploadFolderId',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -160,10 +160,10 @@ export const OneDriveBlock: BlockConfig = {
condition: { field: 'operation', value: ['create_file', 'upload'] },
},
{
- id: 'manualFolderId',
+ id: 'uploadManualFolderId',
title: 'Parent Folder ID',
type: 'short-input',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'uploadFolderId',
placeholder: 'Enter parent folder ID (leave empty for root folder)',
dependsOn: ['credential'],
mode: 'advanced',
@@ -209,10 +209,10 @@ export const OneDriveBlock: BlockConfig = {
},
// List Fields - Folder Selector (basic mode)
{
- id: 'folderSelector',
+ id: 'listFolderSelector',
title: 'Select Folder',
type: 'file-selector',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'listFolderId',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -230,10 +230,10 @@ export const OneDriveBlock: BlockConfig = {
},
// Manual Folder ID input (advanced mode)
{
- id: 'manualFolderId',
+ id: 'listManualFolderId',
title: 'Folder ID',
type: 'short-input',
- canonicalParamId: 'folderId',
+ canonicalParamId: 'listFolderId',
placeholder: 'Enter folder ID (leave empty for root folder)',
dependsOn: ['credential'],
mode: 'advanced',
@@ -273,6 +273,7 @@ export const OneDriveBlock: BlockConfig = {
mode: 'basic',
dependsOn: ['credential'],
condition: { field: 'operation', value: 'download' },
+ required: true,
},
// Manual File ID input (advanced mode)
{
@@ -294,10 +295,10 @@ export const OneDriveBlock: BlockConfig = {
},
// Delete File Fields - File Selector (basic mode)
{
- id: 'fileSelector',
+ id: 'deleteFileSelector',
title: 'Select File to Delete',
type: 'file-selector',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'deleteFileId',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -316,10 +317,10 @@ export const OneDriveBlock: BlockConfig = {
},
// Manual File ID input (advanced mode)
{
- id: 'manualFileId',
+ id: 'deleteManualFileId',
title: 'File ID',
type: 'short-input',
- canonicalParamId: 'fileId',
+ canonicalParamId: 'deleteFileId',
placeholder: 'Enter file or folder ID to delete',
mode: 'advanced',
condition: { field: 'operation', value: 'delete' },
@@ -355,8 +356,10 @@ export const OneDriveBlock: BlockConfig = {
params: (params) => {
const {
credential,
- folderId,
- fileId,
+ folderSelector,
+ manualFolderId,
+ fileSelector,
+ manualFileId,
mimeType,
values,
downloadFileName,
@@ -373,13 +376,19 @@ export const OneDriveBlock: BlockConfig = {
// Normalize file input from both basic (file-upload) and advanced (short-input) modes
const normalizedFile = normalizeFileInput(file || fileReference, { single: true })
+ // Resolve folder ID from selector (basic) or manual input (advanced)
+ const resolvedFolderId = folderSelector || manualFolderId || undefined
+
+ // Resolve file ID from selector (basic) or manual input (advanced)
+ const resolvedFileId = fileSelector || manualFileId || undefined
+
return {
credential,
...rest,
values: normalizedValues,
file: normalizedFile,
- folderId: folderId || undefined,
- fileId: fileId || undefined,
+ folderId: resolvedFolderId,
+ fileId: resolvedFileId,
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
mimeType: mimeType,
...(downloadFileName && { fileName: downloadFileName }),
@@ -390,16 +399,23 @@ export const OneDriveBlock: BlockConfig = {
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Microsoft account credential' },
- // Upload and Create Folder operation inputs
+ // Upload and Create operation inputs
fileName: { type: 'string', description: 'File name' },
file: { type: 'json', description: 'File to upload (UserFile object)' },
fileReference: { type: 'json', description: 'File reference from previous block' },
content: { type: 'string', description: 'Text content to upload' },
mimeType: { type: 'string', description: 'MIME type of file to create' },
values: { type: 'json', description: 'Cell values for new Excel as JSON' },
- fileId: { type: 'string', description: 'File ID to download' },
+ // Folder canonical params (per-operation)
+ uploadFolderId: { type: 'string', description: 'Parent folder for upload/create' },
+ createFolderParentId: { type: 'string', description: 'Parent folder for create folder' },
+ listFolderId: { type: 'string', description: 'Folder to list files from' },
+ // File canonical params (per-operation)
+ downloadFileId: { type: 'string', description: 'File to download' },
+ deleteFileId: { type: 'string', description: 'File to delete' },
downloadFileName: { type: 'string', description: 'File name override for download' },
- folderId: { type: 'string', description: 'Folder ID' },
+ folderName: { type: 'string', description: 'Folder name for create_folder' },
+ // List operation inputs
query: { type: 'string', description: 'Search query' },
pageSize: { type: 'number', description: 'Results per page' },
},
diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts
index e22c654c4..622667d9f 100644
--- a/apps/sim/serializer/index.ts
+++ b/apps/sim/serializer/index.ts
@@ -520,7 +520,9 @@ export class Serializer {
}
// Check if value is missing
- const fieldValue = params[subBlockConfig.id]
+ // For canonical subBlocks, look up the canonical param value (original IDs were deleted)
+ const canonicalId = canonicalIndex.canonicalIdBySubBlockId[subBlockConfig.id]
+ const fieldValue = canonicalId ? params[canonicalId] : params[subBlockConfig.id]
if (fieldValue === undefined || fieldValue === null || fieldValue === '') {
missingFields.push(subBlockConfig.title || subBlockConfig.id)
}