diff --git a/apps/sim/app/api/tools/monday/boards/route.ts b/apps/sim/app/api/tools/monday/boards/route.ts
new file mode 100644
index 0000000000..ea7d412590
--- /dev/null
+++ b/apps/sim/app/api/tools/monday/boards/route.ts
@@ -0,0 +1,57 @@
+import { NextResponse } from 'next/server'
+import { createLogger } from '@sim/logger'
+import { generateRequestId } from '@/lib/core/utils/request'
+import { executeMondayQuery, QUERIES } from '@/tools/monday/graphql'
+
+export const dynamic = 'force-dynamic'
+
+const logger = createLogger('MondayBoardsAPI')
+
+interface MondayBoard {
+ id: string
+ name: string
+ description?: string
+ board_kind: string
+ state: string
+}
+
+/**
+ * POST /api/tools/monday/boards
+ * Fetches active boards from a Monday.com account
+ *
+ * @param request - Request containing the Monday.com API key
+ * @returns JSON response with list of active boards
+ */
+export async function POST(request: Request) {
+ try {
+ const requestId = generateRequestId()
+ const body = await request.json()
+ const { apiKey } = body
+
+ if (!apiKey) {
+ logger.error('Missing API key in request')
+ return NextResponse.json({ error: 'API key is required' }, { status: 400 })
+ }
+
+ logger.info('Fetching Monday.com boards', { requestId })
+
+ const data = await executeMondayQuery<{ boards: MondayBoard[] }>(apiKey, {
+ query: QUERIES.GET_BOARDS,
+ })
+
+ const boards = (data.boards || [])
+ .filter((board) => board.state === 'active')
+ .map((board) => ({
+ id: board.id,
+ name: board.name,
+ description: board.description,
+ kind: board.board_kind,
+ }))
+
+ logger.info(`Successfully fetched ${boards.length} Monday.com boards`, { requestId })
+ return NextResponse.json({ items: boards })
+ } catch (error) {
+ logger.error('Error fetching Monday.com boards:', error)
+ return NextResponse.json({ error: 'Failed to retrieve Monday.com boards' }, { status: 500 })
+ }
+}
diff --git a/apps/sim/app/api/tools/monday/columns/route.ts b/apps/sim/app/api/tools/monday/columns/route.ts
new file mode 100644
index 0000000000..11883473c7
--- /dev/null
+++ b/apps/sim/app/api/tools/monday/columns/route.ts
@@ -0,0 +1,62 @@
+import { NextResponse } from 'next/server'
+import { createLogger } from '@sim/logger'
+import { generateRequestId } from '@/lib/core/utils/request'
+import { executeMondayQuery, QUERIES } from '@/tools/monday/graphql'
+
+export const dynamic = 'force-dynamic'
+
+const logger = createLogger('MondayColumnsAPI')
+
+interface MondayColumn {
+ id: string
+ title: string
+ type: string
+ settings_str?: string
+}
+
+export async function POST(request: Request) {
+ try {
+ const requestId = generateRequestId()
+ const body = await request.json()
+ const { apiKey, boardId } = body
+
+ if (!apiKey) {
+ logger.error('Missing API key in request')
+ return NextResponse.json({ error: 'API key is required' }, { status: 400 })
+ }
+
+ if (!boardId) {
+ logger.error('Missing board ID in request')
+ return NextResponse.json({ error: 'Board ID is required' }, { status: 400 })
+ }
+
+ const parsedBoardId = parseInt(boardId, 10)
+ if (isNaN(parsedBoardId)) {
+ logger.error('Invalid board ID format', { boardId })
+ return NextResponse.json({ error: 'Board ID must be a valid number' }, { status: 400 })
+ }
+
+ logger.info('Fetching Monday.com columns', { requestId, boardId: parsedBoardId })
+
+ const data = await executeMondayQuery<{ boards: Array<{ columns: MondayColumn[] }> }>(
+ apiKey,
+ {
+ query: QUERIES.GET_BOARD_COLUMNS,
+ variables: { boardId: [parsedBoardId] },
+ }
+ )
+
+ const columns = data.boards?.[0]?.columns || []
+ const formattedColumns = columns.map((col) => ({
+ id: col.id,
+ name: col.title,
+ type: col.type,
+ }))
+
+ logger.info(`Successfully fetched ${formattedColumns.length} columns`, { requestId })
+ return NextResponse.json({ items: formattedColumns })
+ } catch (error) {
+ logger.error('Error fetching Monday.com columns:', error)
+ return NextResponse.json({ error: 'Failed to retrieve columns' }, { status: 500 })
+ }
+}
diff --git a/apps/sim/app/api/tools/monday/groups/route.ts b/apps/sim/app/api/tools/monday/groups/route.ts
new file mode 100644
index 0000000000..60c56aaaac
--- /dev/null
+++ b/apps/sim/app/api/tools/monday/groups/route.ts
@@ -0,0 +1,55 @@
+import { NextResponse } from 'next/server'
+import { createLogger } from '@sim/logger'
+import { generateRequestId } from '@/lib/core/utils/request'
+import { executeMondayQuery, QUERIES } from '@/tools/monday/graphql'
+
+export const dynamic = 'force-dynamic'
+
+const logger = createLogger('MondayGroupsAPI')
+
+interface MondayGroup {
+ id: string
+ title: string
+ color: string
+}
+
+export async function POST(request: Request) {
+ try {
+ const requestId = generateRequestId()
+ const body = await request.json()
+ const { apiKey, boardId } = body
+
+ if (!apiKey || !boardId) {
+ return NextResponse.json(
+ { error: 'API key and board ID are required' },
+ { status: 400 }
+ )
+ }
+
+ logger.info('Fetching Monday.com groups', { requestId, boardId })
+
+ const data = await executeMondayQuery<{ boards: Array<{ groups: MondayGroup[] }> }>(
+ apiKey,
+ {
+ query: QUERIES.GET_BOARD_GROUPS,
+ variables: { boardId: [parseInt(boardId, 10)] },
+ }
+ )
+
+ const groups = data.boards?.[0]?.groups || []
+ const formattedGroups = groups.map((group) => ({
+ id: group.id,
+ name: group.title,
+ color: group.color,
+ }))
+
+ logger.info(`Successfully fetched ${formattedGroups.length} groups`, { requestId })
+ return NextResponse.json({ items: formattedGroups })
+ } catch (error) {
+ logger.error('Error fetching Monday.com groups:', error)
+ return NextResponse.json(
+ { error: 'Failed to retrieve groups', details: (error as Error).message },
+ { status: 500 }
+ )
+ }
+}
diff --git a/apps/sim/app/api/tools/monday/items/route.ts b/apps/sim/app/api/tools/monday/items/route.ts
new file mode 100644
index 0000000000..c977c53457
--- /dev/null
+++ b/apps/sim/app/api/tools/monday/items/route.ts
@@ -0,0 +1,85 @@
+import { createLogger } from '@sim/logger'
+import { NextResponse } from 'next/server'
+import { QUERIES } from '@/tools/monday/graphql'
+
+const logger = createLogger('MondayItemsAPI')
+
+interface MondayItem {
+ id: string
+ name: string
+}
+
+/**
+ * POST /api/tools/monday/items
+ * Fetches items from a Monday.com board for selector dropdown
+ */
+export async function POST(request: Request) {
+ try {
+ const body = await request.json()
+ const { apiKey, boardId } = body
+
+ if (!apiKey) {
+ logger.warn('Missing apiKey in request')
+ return NextResponse.json({ error: 'API key is required' }, { status: 400 })
+ }
+
+ if (!boardId) {
+ logger.warn('Missing boardId in request')
+ return NextResponse.json({ error: 'Board ID is required' }, { status: 400 })
+ }
+
+ logger.info('Fetching Monday.com items', { boardId })
+
+ const response = await fetch('https://api.monday.com/v2', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: apiKey,
+ 'API-Version': '2024-01',
+ },
+ body: JSON.stringify({
+ query: QUERIES.GET_BOARD_ITEMS,
+ variables: {
+ boardId: [parseInt(boardId, 10)],
+ limit: 100,
+ },
+ }),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ logger.error('Monday.com API error', {
+ status: response.status,
+ error: errorText,
+ })
+ return NextResponse.json(
+ { error: `Monday.com API error: ${response.status}` },
+ { status: response.status }
+ )
+ }
+
+ const result = await response.json()
+
+ if (result.errors) {
+ logger.error('Monday.com GraphQL errors', { errors: result.errors })
+ return NextResponse.json(
+ { error: 'Failed to fetch items', details: result.errors },
+ { status: 400 }
+ )
+ }
+
+ const items = result.data?.boards?.[0]?.items_page?.items || []
+
+ logger.info('Successfully fetched Monday.com items', { count: items.length })
+
+ return NextResponse.json({
+ items: items.map((item: MondayItem) => ({
+ id: item.id,
+ name: item.name,
+ })),
+ })
+ } catch (error) {
+ logger.error('Unexpected error fetching Monday.com items', { error })
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
diff --git a/apps/sim/app/api/tools/monday/status-options/route.ts b/apps/sim/app/api/tools/monday/status-options/route.ts
new file mode 100644
index 0000000000..aa26c63b44
--- /dev/null
+++ b/apps/sim/app/api/tools/monday/status-options/route.ts
@@ -0,0 +1,104 @@
+import { NextResponse } from 'next/server'
+import { createLogger } from '@sim/logger'
+import { generateRequestId } from '@/lib/core/utils/request'
+import { executeMondayQuery, QUERIES } from '@/tools/monday/graphql'
+
+export const dynamic = 'force-dynamic'
+
+const logger = createLogger('MondayStatusOptionsAPI')
+
+interface MondayColumn {
+ id: string
+ title: string
+ type: string
+ settings_str?: string
+}
+
+interface StatusLabel {
+ id: string
+ label: string
+ color: string
+}
+
+interface StatusLabelSettings {
+ label: string
+ color?: string
+}
+
+export async function POST(request: Request) {
+ try {
+ const requestId = generateRequestId()
+ const body = await request.json()
+ const { apiKey, boardId, columnId } = body
+
+ if (!apiKey || !boardId || !columnId) {
+ return NextResponse.json(
+ { error: 'API key, board ID, and column ID are required' },
+ { status: 400 }
+ )
+ }
+
+ logger.info('Fetching Monday.com status options', { requestId, boardId, columnId })
+
+ const data = await executeMondayQuery<{ boards: Array<{ columns: MondayColumn[] }> }>(
+ apiKey,
+ {
+ query: QUERIES.GET_COLUMN_SETTINGS,
+ variables: {
+ boardId: [parseInt(boardId, 10)],
+ columnId,
+ },
+ }
+ )
+
+ const column = data.boards?.[0]?.columns?.[0]
+
+ if (!column) {
+ return NextResponse.json({ error: 'Column not found' }, { status: 404 })
+ }
+
+ if (column.type !== 'status' && column.type !== 'color') {
+ return NextResponse.json(
+ { error: `Column type ${column.type} does not have status options` },
+ { status: 400 }
+ )
+ }
+
+ let statusOptions: StatusLabel[] = []
+
+ if (column.settings_str) {
+ try {
+ const settings = JSON.parse(column.settings_str)
+ const labels = settings.labels || {}
+
+ statusOptions = Object.entries(labels).map(([id, label]: [string, StatusLabelSettings | string]) => {
+ if (typeof label === 'string') {
+ return { id, label, color: '#000000' }
+ }
+ return {
+ id,
+ label: label.label,
+ color: label.color || '#000000',
+ }
+ })
+ } catch (parseError) {
+ logger.error('Failed to parse column settings', {
+ error: parseError,
+ settings_str: column.settings_str,
+ })
+ }
+ }
+
+ logger.info(`Successfully fetched ${statusOptions.length} status options`, { requestId })
+ return NextResponse.json({
+ items: statusOptions.map((option) => ({
+ id: option.id,
+ name: option.label,
+ color: option.color,
+ })),
+ })
+ } catch (error) {
+ logger.error('Error fetching Monday.com status options:', error)
+ return NextResponse.json({ error: 'Failed to retrieve status options' }, { status: 500 })
+ }
+}
diff --git a/apps/sim/app/api/tools/monday/subitems/route.ts b/apps/sim/app/api/tools/monday/subitems/route.ts
new file mode 100644
index 0000000000..52ead996b4
--- /dev/null
+++ b/apps/sim/app/api/tools/monday/subitems/route.ts
@@ -0,0 +1,84 @@
+import { createLogger } from '@sim/logger'
+import { NextResponse } from 'next/server'
+import { QUERIES } from '@/tools/monday/graphql'
+
+const logger = createLogger('MondaySubitemsAPI')
+
+interface MondaySubitem {
+ id: string
+ name: string
+}
+
+/**
+ * POST /api/tools/monday/subitems
+ * Fetches subitems from a Monday.com item for selector dropdown
+ */
+export async function POST(request: Request) {
+ try {
+ const body = await request.json()
+ const { apiKey, itemId } = body
+
+ if (!apiKey) {
+ logger.warn('Missing apiKey in request')
+ return NextResponse.json({ error: 'API key is required' }, { status: 400 })
+ }
+
+ if (!itemId) {
+ logger.warn('Missing itemId in request')
+ return NextResponse.json({ error: 'Item ID is required' }, { status: 400 })
+ }
+
+ logger.info('Fetching Monday.com subitems', { itemId })
+
+ const response = await fetch('https://api.monday.com/v2', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: apiKey,
+ 'API-Version': '2024-01',
+ },
+ body: JSON.stringify({
+ query: QUERIES.GET_ITEM_SUBITEMS,
+ variables: {
+ itemId: [parseInt(itemId, 10)],
+ },
+ }),
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text()
+ logger.error('Monday.com API error', {
+ status: response.status,
+ error: errorText,
+ })
+ return NextResponse.json(
+ { error: `Monday.com API error: ${response.status}` },
+ { status: response.status }
+ )
+ }
+
+ const result = await response.json()
+
+ if (result.errors) {
+ logger.error('Monday.com GraphQL errors', { errors: result.errors })
+ return NextResponse.json(
+ { error: 'Failed to fetch subitems', details: result.errors },
+ { status: 400 }
+ )
+ }
+
+ const subitems = result.data?.items?.[0]?.subitems || []
+
+ logger.info('Successfully fetched Monday.com subitems', { count: subitems.length })
+
+ return NextResponse.json({
+ items: subitems.map((subitem: MondaySubitem) => ({
+ id: subitem.id,
+ name: subitem.name,
+ })),
+ })
+ } catch (error) {
+ logger.error('Unexpected error fetching Monday.com subitems', { error })
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx
index 27415b31a4..d0ce42dc14 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx
@@ -49,6 +49,20 @@ export function FileSelectorInput({
const [teamIdValueFromStore] = useSubBlockValue(blockId, 'teamId')
const [siteIdValueFromStore] = useSubBlockValue(blockId, 'siteId')
const [collectionIdValueFromStore] = useSubBlockValue(blockId, 'collectionId')
+ const [apiKeyValueFromStore] = useSubBlockValue(blockId, 'apiKey')
+ const [boardIdValueFromStore] = useSubBlockValue(blockId, 'board_id')
+ const [boardIdCamelFromStore] = useSubBlockValue(blockId, 'boardId')
+ const [boardIdListFromStore] = useSubBlockValue(blockId, 'board_id_list')
+ const [boardIdUpdateFromStore] = useSubBlockValue(blockId, 'board_id_update')
+ const [boardIdGetFromStore] = useSubBlockValue(blockId, 'board_id_get')
+ const [groupIdValueFromStore] = useSubBlockValue(blockId, 'group_id')
+ const [groupIdCamelFromStore] = useSubBlockValue(blockId, 'groupId')
+ const [groupIdListFromStore] = useSubBlockValue(blockId, 'group_id_list')
+ const [columnIdValueFromStore] = useSubBlockValue(blockId, 'column_id')
+ const [columnIdCamelFromStore] = useSubBlockValue(blockId, 'columnId')
+ const [itemIdValueFromStore] = useSubBlockValue(blockId, 'item_id')
+ const [itemIdCamelFromStore] = useSubBlockValue(blockId, 'itemId')
+ const [itemIdGetFromStore] = useSubBlockValue(blockId, 'item_id_get')
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
const domainValue = previewContextValues?.domain ?? domainValueFromStore
@@ -57,6 +71,32 @@ export function FileSelectorInput({
const teamIdValue = previewContextValues?.teamId ?? teamIdValueFromStore
const siteIdValue = previewContextValues?.siteId ?? siteIdValueFromStore
const collectionIdValue = previewContextValues?.collectionId ?? collectionIdValueFromStore
+ const apiKeyValue = previewContextValues?.apiKey ?? apiKeyValueFromStore
+ const boardIdValue =
+ previewContextValues?.board_id ??
+ previewContextValues?.boardId ??
+ boardIdValueFromStore ??
+ boardIdCamelFromStore ??
+ boardIdListFromStore ??
+ boardIdUpdateFromStore ??
+ boardIdGetFromStore
+ const groupIdValue =
+ previewContextValues?.group_id ??
+ previewContextValues?.groupId ??
+ groupIdValueFromStore ??
+ groupIdCamelFromStore ??
+ groupIdListFromStore
+ const columnIdValue =
+ previewContextValues?.column_id ??
+ previewContextValues?.columnId ??
+ columnIdValueFromStore ??
+ columnIdCamelFromStore
+ const itemIdValue =
+ previewContextValues?.item_id ??
+ previewContextValues?.itemId ??
+ itemIdValueFromStore ??
+ itemIdCamelFromStore ??
+ itemIdGetFromStore
const normalizedCredentialId =
typeof connectedCredential === 'string'
@@ -81,6 +121,11 @@ export function FileSelectorInput({
teamId: (teamIdValue as string) || undefined,
siteId: (siteIdValue as string) || undefined,
collectionId: (collectionIdValue as string) || undefined,
+ apiKey: (apiKeyValue as string) || undefined,
+ boardId: (boardIdValue as string) || undefined,
+ groupId: (groupIdValue as string) || undefined,
+ columnId: (columnIdValue as string) || undefined,
+ itemId: (itemIdValue as string) || undefined,
})
}, [
subBlock,
@@ -92,9 +137,16 @@ export function FileSelectorInput({
teamIdValue,
siteIdValue,
collectionIdValue,
+ apiKeyValue,
+ boardIdValue,
+ groupIdValue,
+ columnIdValue,
+ itemIdValue,
])
- const missingCredential = !normalizedCredentialId
+ const isMondaySelector = selectorResolution?.key?.startsWith('monday.')
+ const missingCredential = !isMondaySelector && !normalizedCredentialId
+ const missingApiKey = isMondaySelector && !selectorResolution?.context.apiKey
const missingDomain =
selectorResolution?.key &&
(selectorResolution.key === 'confluence.pages' || selectorResolution.key === 'jira.issues') &&
@@ -109,16 +161,34 @@ export function FileSelectorInput({
selectorResolution?.key === 'webflow.collections' && !selectorResolution.context.siteId
const missingCollection =
selectorResolution?.key === 'webflow.items' && !selectorResolution.context.collectionId
+ const missingBoard =
+ isMondaySelector &&
+ (selectorResolution?.key === 'monday.groups' ||
+ selectorResolution?.key === 'monday.columns' ||
+ selectorResolution?.key === 'monday.items') &&
+ !selectorResolution?.context.boardId
+ const missingColumn =
+ isMondaySelector &&
+ selectorResolution?.key === 'monday.status-options' &&
+ !selectorResolution?.context.columnId
+ const missingItem =
+ isMondaySelector &&
+ selectorResolution?.key === 'monday.subitems' &&
+ !selectorResolution?.context.itemId
const disabledReason =
finalDisabled ||
isForeignCredential ||
missingCredential ||
+ missingApiKey ||
missingDomain ||
missingProject ||
missingPlan ||
missingSite ||
missingCollection ||
+ missingBoard ||
+ missingColumn ||
+ missingItem ||
!selectorResolution?.key
if (!selectorResolution?.key) {
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx
index f72930bdc6..7ecef4f6df 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox.tsx
@@ -1,5 +1,6 @@
import type React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { createLogger } from '@sim/logger'
import { Combobox as EditableCombobox } from '@/components/emcn/components'
import { SubBlockInputController } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sub-block-input-controller'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
@@ -11,6 +12,8 @@ import {
useSelectorOptions,
} from '@/hooks/selectors/use-selector-query'
+const logger = createLogger('SelectorCombobox')
+
interface SelectorComboboxProps {
blockId: string
subBlock: SubBlockConfig
@@ -38,6 +41,60 @@ export function SelectorCombobox({
onOptionChange,
allowSearch = true,
}: SelectorComboboxProps) {
+ // For Monday.com selectors, read apiKey and boardId directly from block state
+ const [apiKeyFromBlock] = useSubBlockValue(blockId, 'apiKey')
+ const [boardIdFromBlock] = useSubBlockValue(blockId, 'board_id')
+ const [boardIdCamelFromBlock] = useSubBlockValue(blockId, 'boardId')
+ const [columnIdFromBlock] = useSubBlockValue(blockId, 'column_id')
+ const [columnIdCamelFromBlock] = useSubBlockValue(blockId, 'columnId')
+
+ // Merge Monday.com specific values into context if they're missing
+ const enrichedContext = useMemo(
+ () =>
+ selectorKey.startsWith('monday.')
+ ? {
+ ...selectorContext,
+ apiKey: selectorContext.apiKey || apiKeyFromBlock,
+ boardId: selectorContext.boardId || boardIdFromBlock || boardIdCamelFromBlock,
+ columnId: selectorContext.columnId || columnIdFromBlock || columnIdCamelFromBlock,
+ }
+ : selectorContext,
+ [
+ selectorKey,
+ selectorContext,
+ apiKeyFromBlock,
+ boardIdFromBlock,
+ boardIdCamelFromBlock,
+ columnIdFromBlock,
+ columnIdCamelFromBlock,
+ ]
+ )
+
+ // For Monday selectors, override disabled if we have apiKey and required dependencies
+ let actualDisabled = disabled
+ if (selectorKey.startsWith('monday.')) {
+ if (selectorKey === 'monday.boards') {
+ // boards only needs apiKey
+ actualDisabled = !enrichedContext.apiKey
+ } else if (selectorKey === 'monday.columns' || selectorKey === 'monday.groups') {
+ // columns/groups need apiKey AND boardId
+ actualDisabled = !enrichedContext.apiKey || !enrichedContext.boardId
+ } else if (selectorKey === 'monday.status-options') {
+ // status-options need apiKey, boardId, AND columnId
+ actualDisabled = !enrichedContext.apiKey || !enrichedContext.boardId || !enrichedContext.columnId
+ }
+ }
+
+ logger.info('SelectorCombobox render', {
+ subBlockId: subBlock.id,
+ selectorKey,
+ disabled,
+ actualDisabled,
+ hasApiKey: !!enrichedContext.apiKey,
+ apiKeyFromBlock,
+ enrichedContext,
+ })
+
const [storeValueRaw, setStoreValue] = useSubBlockValue(
blockId,
subBlock.id
@@ -52,11 +109,11 @@ export function SelectorCombobox({
isLoading,
error,
} = useSelectorOptions(selectorKey, {
- context: selectorContext,
+ context: enrichedContext,
search: allowSearch ? searchTerm : undefined,
})
const { data: detailOption } = useSelectorOptionDetail(selectorKey, {
- context: selectorContext,
+ context: enrichedContext,
detailId: activeValue,
})
const optionMap = useSelectorOptionMap(options, detailOption ?? undefined)
@@ -89,12 +146,12 @@ export function SelectorCombobox({
const handleSelection = useCallback(
(value: string) => {
- if (readOnly || disabled) return
+ if (readOnly || actualDisabled) return
setStoreValue(value)
setIsEditing(false)
onOptionChange?.(value)
},
- [setStoreValue, onOptionChange, readOnly, disabled]
+ [setStoreValue, onOptionChange, readOnly, actualDisabled]
)
return (
@@ -104,7 +161,7 @@ export function SelectorCombobox({
subBlockId={subBlock.id}
config={subBlock}
value={activeValue ?? ''}
- disabled={disabled || readOnly}
+ disabled={actualDisabled || readOnly}
isPreview={isPreview}
>
{({ ref, onDrop, onDragOver }) => (
@@ -127,7 +184,7 @@ export function SelectorCombobox({
}
}}
placeholder={placeholder || subBlock.placeholder || 'Select an option'}
- disabled={disabled || readOnly}
+ disabled={actualDisabled || readOnly}
editable={allowSearch}
filterOptions={allowSearch}
inputRef={ref as React.RefObject}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts
index 3c145f52fc..3c22641474 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate.ts
@@ -1,10 +1,13 @@
'use client'
import { useMemo } from 'react'
+import { createLogger } from '@sim/logger'
import type { SubBlockConfig } from '@/blocks/types'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
+const logger = createLogger('useDependsOnGate')
+
type DependsOnConfig = string[] | { all?: string[]; any?: string[] }
/**
@@ -144,9 +147,22 @@ export function useDependsOnGate(
const finalDisabled = disabledProp || isPreview || blocked
+ // Debug logging for dependency issues
+ if (typeof window !== 'undefined' && allDependsOnFields.includes('apiKey')) {
+ logger.info('Dependency gate debug', {
+ subBlockId: subBlock.id,
+ allDependsOnFields,
+ dependencyValuesMap,
+ depsSatisfied,
+ blocked,
+ finalDisabled,
+ })
+ }
+
return {
dependsOn,
dependencyValues,
+ dependencyValuesMap,
depsSatisfied,
blocked,
finalDisabled,
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx
index 310c3df0dd..1478255db5 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx
@@ -1,5 +1,6 @@
import { type JSX, type MouseEvent, memo, useRef, useState } from 'react'
import { AlertTriangle, Wand2 } from 'lucide-react'
+import { createLogger } from '@sim/logger'
import { Label, Tooltip } from '@/components/emcn/components'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/core/utils/cn'
@@ -44,6 +45,8 @@ import {
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import type { SubBlockConfig } from '@/blocks/types'
+const logger = createLogger('SubBlock')
+
/**
* Interface for wand control handlers exposed by sub-block inputs
*/
@@ -392,7 +395,7 @@ function SubBlockComponent({
// Use dependsOn gating to compute final disabled state
// Only pass previewContextValues when in preview mode to avoid format mismatches
- const { finalDisabled: gatedDisabled } = useDependsOnGate(blockId, config, {
+ const { finalDisabled: gatedDisabled, dependencyValuesMap, depsSatisfied } = useDependsOnGate(blockId, config, {
disabled,
isPreview,
previewContextValues: isPreview ? subBlockValues : undefined,
@@ -400,6 +403,19 @@ function SubBlockComponent({
const isDisabled = gatedDisabled
+ // Debug logging for Monday selector issues
+ if (config.serviceId === 'monday' && typeof window !== 'undefined') {
+ logger.info('Sub-block debug', {
+ subBlockId: config.id,
+ type: config.type,
+ dependsOn: config.dependsOn,
+ dependencyValuesMap,
+ depsSatisfied,
+ gatedDisabled,
+ isDisabled,
+ })
+ }
+
/**
* Selects and renders the appropriate input component based on config.type.
*
@@ -656,6 +672,12 @@ function SubBlockComponent({
)
case 'file-selector':
+ logger.info('Rendering file-selector', {
+ subBlockId: config.id,
+ serviceId: config.serviceId,
+ isDisabled,
+ dependsOn: config.dependsOn,
+ })
return (
{config.note}
+ )}
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/sub-block/components/file-selector/file-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/sub-block/components/file-selector/file-selector-input.tsx
index 3f4dab8519..6f664f600d 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/sub-block/components/file-selector/file-selector-input.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/sub-block/components/file-selector/file-selector-input.tsx
@@ -31,6 +31,8 @@ export function FileSelectorInput({
previewValue,
previewContextValues,
}: FileSelectorInputProps) {
+ console.log('[FileSelectorInput RENDER]', { subBlockId: subBlock.id, serviceId: subBlock.serviceId })
+
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
const { activeWorkflowId } = useWorkflowRegistry()
const params = useParams()
@@ -49,6 +51,16 @@ export function FileSelectorInput({
const [teamIdValueFromStore] = useSubBlockValue(blockId, 'teamId')
const [siteIdValueFromStore] = useSubBlockValue(blockId, 'siteId')
const [collectionIdValueFromStore] = useSubBlockValue(blockId, 'collectionId')
+ const [apiKeyValueFromStore] = useSubBlockValue(blockId, 'apiKey')
+ const [boardIdValueFromStore] = useSubBlockValue(blockId, 'board_id')
+ const [boardIdCamelFromStore] = useSubBlockValue(blockId, 'boardId')
+ const [boardIdListFromStore] = useSubBlockValue(blockId, 'board_id_list')
+ const [boardIdUpdateFromStore] = useSubBlockValue(blockId, 'board_id_update')
+ const [groupIdValueFromStore] = useSubBlockValue(blockId, 'group_id')
+ const [groupIdCamelFromStore] = useSubBlockValue(blockId, 'groupId')
+ const [groupIdListFromStore] = useSubBlockValue(blockId, 'group_id_list')
+ const [columnIdValueFromStore] = useSubBlockValue(blockId, 'column_id')
+ const [columnIdCamelFromStore] = useSubBlockValue(blockId, 'columnId')
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
const domainValue = previewContextValues?.domain ?? domainValueFromStore
@@ -57,6 +69,25 @@ export function FileSelectorInput({
const teamIdValue = previewContextValues?.teamId ?? teamIdValueFromStore
const siteIdValue = previewContextValues?.siteId ?? siteIdValueFromStore
const collectionIdValue = previewContextValues?.collectionId ?? collectionIdValueFromStore
+ const apiKeyValue = previewContextValues?.apiKey ?? apiKeyValueFromStore
+ const boardIdValue =
+ previewContextValues?.board_id ??
+ previewContextValues?.boardId ??
+ boardIdValueFromStore ??
+ boardIdCamelFromStore ??
+ boardIdListFromStore ??
+ boardIdUpdateFromStore
+ const groupIdValue =
+ previewContextValues?.group_id ??
+ previewContextValues?.groupId ??
+ groupIdValueFromStore ??
+ groupIdCamelFromStore ??
+ groupIdListFromStore
+ const columnIdValue =
+ previewContextValues?.column_id ??
+ previewContextValues?.columnId ??
+ columnIdValueFromStore ??
+ columnIdCamelFromStore
const normalizedCredentialId =
typeof connectedCredential === 'string'
@@ -81,6 +112,10 @@ export function FileSelectorInput({
teamId: (teamIdValue as string) || undefined,
siteId: (siteIdValue as string) || undefined,
collectionId: (collectionIdValue as string) || undefined,
+ apiKey: (apiKeyValue as string) || undefined,
+ boardId: (boardIdValue as string) || undefined,
+ groupId: (groupIdValue as string) || undefined,
+ columnId: (columnIdValue as string) || undefined,
})
}, [
subBlock,
@@ -92,9 +127,43 @@ export function FileSelectorInput({
teamIdValue,
siteIdValue,
collectionIdValue,
+ apiKeyValue,
+ boardIdValue,
+ groupIdValue,
+ columnIdValue,
+ boardIdValueFromStore,
+ boardIdCamelFromStore,
+ boardIdListFromStore,
+ boardIdUpdateFromStore,
+ groupIdValueFromStore,
+ groupIdCamelFromStore,
+ groupIdListFromStore,
+ columnIdValueFromStore,
+ columnIdCamelFromStore,
])
- const missingCredential = !normalizedCredentialId
+ const isMondaySelector = selectorResolution?.key?.startsWith('monday.')
+ const missingCredential = !isMondaySelector && !normalizedCredentialId
+ const missingApiKey = isMondaySelector && !selectorResolution?.context.apiKey
+
+ // Debug logging for Monday selectors
+ if (isMondaySelector && typeof window !== 'undefined') {
+ console.log('[Monday Selector Debug]', {
+ subBlockId: subBlock.id,
+ selectorKey: selectorResolution?.key,
+ apiKeyFromStore: apiKeyValueFromStore,
+ apiKeyValue: apiKeyValue,
+ contextApiKey: selectorResolution?.context.apiKey,
+ missingApiKey,
+ finalDisabled,
+ disabledReason: {
+ finalDisabled,
+ isForeignCredential,
+ missingCredential,
+ missingApiKey,
+ },
+ })
+ }
const missingDomain =
selectorResolution?.key &&
(selectorResolution.key === 'confluence.pages' || selectorResolution.key === 'jira.issues') &&
@@ -109,16 +178,21 @@ export function FileSelectorInput({
selectorResolution?.key === 'webflow.collections' && !selectorResolution?.context.siteId
const missingCollection =
selectorResolution?.key === 'webflow.items' && !selectorResolution?.context.collectionId
+ const missingBoardId =
+ (selectorResolution?.key === 'monday.columns' || selectorResolution?.key === 'monday.groups') &&
+ !selectorResolution?.context.boardId
const disabledReason =
finalDisabled ||
- isForeignCredential ||
+ (!isMondaySelector && isForeignCredential) ||
missingCredential ||
+ missingApiKey ||
missingDomain ||
missingProject ||
missingPlan ||
missingSite ||
missingCollection ||
+ missingBoardId ||
!selectorResolution?.key
if (!selectorResolution?.key) {
@@ -149,6 +223,12 @@ export function FileSelectorInput({
allowSearch={selectorResolution.allowSearch}
onOptionChange={(value) => {
if (!isPreview) {
+ console.log('[FileSelectorInput] Setting value', {
+ blockId,
+ subBlockId: subBlock.id,
+ value,
+ valueType: typeof value,
+ })
collaborativeSetSubblockValue(blockId, subBlock.id, value)
}
}}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts
index 61af9f0624..8cdc7349a8 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts
@@ -903,7 +903,12 @@ export function useWorkflowExecution() {
loops: executionWorkflowState.loops,
parallels: executionWorkflowState.parallels,
}
- : undefined,
+ : {
+ blocks: filteredStates,
+ edges: workflowEdges,
+ loops: latestWorkflowState.loops || {},
+ parallels: latestWorkflowState.parallels || {},
+ },
callbacks: {
onExecutionStarted: (data) => {
logger.info('Server execution started:', data)
diff --git a/apps/sim/blocks/blocks/monday.ts b/apps/sim/blocks/blocks/monday.ts
new file mode 100644
index 0000000000..b302dbcde0
--- /dev/null
+++ b/apps/sim/blocks/blocks/monday.ts
@@ -0,0 +1,220 @@
+import { MondayIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
+import { AuthMode } from '@/blocks/types'
+import type { MondayResponse } from '@/tools/monday/types'
+import { getTrigger } from '@/triggers'
+
+export const MondayBlock: BlockConfig = {
+ type: 'monday',
+ name: 'Monday',
+ description: 'Create and manage items on Monday boards',
+ authMode: AuthMode.ApiKey,
+ triggerAllowed: true,
+ longDescription:
+ 'Integrate with Monday work management platform. Create items, update column values, list items, and manage your boards programmatically.',
+ docsLink: 'https://docs.monday.com/api',
+ category: 'tools',
+ bgColor: '#FFFFFF',
+ icon: MondayIcon,
+ subBlocks: [
+ {
+ id: 'operation',
+ title: 'Operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Create Item', id: 'monday_create_item' },
+ { label: 'Update Item', id: 'monday_update_item' },
+ { label: 'Get Item', id: 'monday_get_item' },
+ { label: 'List Items', id: 'monday_list_items' },
+ ],
+ value: () => 'monday_create_item',
+ required: true,
+ },
+ {
+ id: 'apiKey',
+ title: 'API Key',
+ type: 'short-input',
+ placeholder: 'Enter your Monday.com API key',
+ password: true,
+ required: true,
+ note: 'The API key can be found by logging into Monday.com, clicking on your profile picture, opening the Developers section. Then, click on Developers in the header menu and select My Access Tokens to get your personal API token.',
+ },
+ // CREATE ITEM fields
+ {
+ id: 'board_id',
+ title: 'Board',
+ type: 'file-selector',
+ serviceId: 'monday',
+ placeholder: 'Select a Monday.com board',
+ required: true,
+ condition: { field: 'operation', value: 'monday_create_item' },
+ dependsOn: ['apiKey'],
+ },
+ {
+ id: 'group_id',
+ title: 'Group (Optional)',
+ type: 'file-selector',
+ serviceId: 'monday',
+ placeholder: 'Select a group/section (optional)',
+ required: false,
+ condition: { field: 'operation', value: 'monday_create_item' },
+ dependsOn: ['apiKey', 'board_id'],
+ },
+ {
+ id: 'item_name',
+ title: 'Item Name',
+ type: 'short-input',
+ placeholder: 'Enter item name',
+ required: true,
+ condition: { field: 'operation', value: 'monday_create_item' },
+ },
+ {
+ id: 'column_values',
+ title: 'Column Values (JSON)',
+ type: 'code',
+ language: 'json',
+ placeholder: '{"status": "Working on it", "text": "Example"}',
+ required: false,
+ condition: { field: 'operation', value: 'monday_create_item' },
+ },
+ // UPDATE ITEM fields
+ {
+ id: 'board_id_update',
+ title: 'Board',
+ type: 'file-selector',
+ serviceId: 'monday',
+ canonicalParamId: 'board_id',
+ placeholder: 'Select a Monday.com board',
+ required: true,
+ condition: { field: 'operation', value: 'monday_update_item' },
+ dependsOn: ['apiKey'],
+ },
+ {
+ id: 'item_id',
+ title: 'Item',
+ type: 'file-selector',
+ serviceId: 'monday',
+ placeholder: 'Select an item to update',
+ required: true,
+ condition: { field: 'operation', value: 'monday_update_item' },
+ dependsOn: ['apiKey', 'board_id_update'],
+ },
+ {
+ id: 'subitem_id',
+ title: 'Sub-Item (Optional)',
+ type: 'file-selector',
+ serviceId: 'monday',
+ placeholder: 'Select a sub-item to update (optional)',
+ required: false,
+ condition: { field: 'operation', value: 'monday_update_item' },
+ dependsOn: ['apiKey', 'item_id'],
+ },
+ {
+ id: 'column_values_update',
+ title: 'Column Values (JSON)',
+ type: 'code',
+ language: 'json',
+ canonicalParamId: 'column_values',
+ placeholder: '{"status": "Done", "text": "Updated"}',
+ required: true,
+ condition: { field: 'operation', value: 'monday_update_item' },
+ },
+ // GET ITEM fields
+ {
+ id: 'board_id_get',
+ title: 'Board',
+ type: 'file-selector',
+ serviceId: 'monday',
+ canonicalParamId: 'board_id',
+ placeholder: 'Select a Monday.com board',
+ required: true,
+ condition: { field: 'operation', value: 'monday_get_item' },
+ dependsOn: ['apiKey'],
+ },
+ {
+ id: 'item_id_get',
+ title: 'Item',
+ type: 'file-selector',
+ serviceId: 'monday',
+ canonicalParamId: 'item_id',
+ placeholder: 'Select an item to retrieve',
+ required: true,
+ condition: { field: 'operation', value: 'monday_get_item' },
+ dependsOn: ['apiKey', 'board_id_get'],
+ },
+ // LIST ITEMS fields
+ {
+ id: 'board_id_list',
+ title: 'Board',
+ type: 'file-selector',
+ serviceId: 'monday',
+ canonicalParamId: 'board_id',
+ placeholder: 'Select a board',
+ required: true,
+ condition: { field: 'operation', value: 'monday_list_items' },
+ dependsOn: ['apiKey'],
+ },
+ {
+ id: 'group_id_list',
+ title: 'Group (Optional)',
+ type: 'file-selector',
+ serviceId: 'monday',
+ canonicalParamId: 'group_id',
+ placeholder: 'Filter by group (optional)',
+ required: false,
+ condition: { field: 'operation', value: 'monday_list_items' },
+ dependsOn: ['apiKey', 'board_id_list'],
+ },
+ {
+ id: 'limit',
+ title: 'Limit',
+ type: 'slider',
+ min: 1,
+ max: 100,
+ step: 1,
+ defaultValue: 25,
+ required: false,
+ condition: { field: 'operation', value: 'monday_list_items' },
+ },
+ // Trigger subBlocks
+ // Include all fields from first trigger (has selectedTriggerId + all fields with conditions)
+ ...getTrigger('monday_new_item').subBlocks,
+ // Skip only selectedTriggerId from second trigger (index 0), keep apiKey and rest
+ ...getTrigger('monday_column_changed').subBlocks.slice(1),
+ ],
+ tools: {
+ access: [
+ 'monday_create_item',
+ 'monday_update_item',
+ 'monday_get_item',
+ 'monday_list_items',
+ ],
+ config: {
+ tool: (params) => {
+ return params.operation || 'monday_create_item'
+ },
+ params: (inputs) => {
+ const { operation, ...rest } = inputs
+ return rest
+ },
+ },
+ },
+ inputs: {
+ operation: { type: 'string', description: 'Operation to perform' },
+ apiKey: { type: 'string', description: 'Monday.com API key' },
+ board_id: { type: 'string', description: 'Board ID' },
+ group_id: { type: 'string', description: 'Group/section ID' },
+ item_id: { type: 'string', description: 'Item ID' },
+ subitem_id: { type: 'string', description: 'Sub-item ID' },
+ item_name: { type: 'string', description: 'Item name' },
+ column_values: { type: 'json', description: 'Column values as JSON' },
+ limit: { type: 'number', description: 'Maximum number of items to return' },
+ },
+ outputs: {
+ success: { type: 'boolean', description: 'Whether operation succeeded' },
+ item: { type: 'json', description: 'Single item object' },
+ items: { type: 'array', description: 'Array of items (for list operation)' },
+ item_id: { type: 'string', description: 'Item ID' },
+ error: { type: 'string', description: 'Error message if failed' },
+ },
+}
diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts
index 737f09bacf..a291e09224 100644
--- a/apps/sim/blocks/registry.ts
+++ b/apps/sim/blocks/registry.ts
@@ -60,6 +60,7 @@ import { KnowledgeBlock } from '@/blocks/blocks/knowledge'
import { LinearBlock } from '@/blocks/blocks/linear'
import { LinkedInBlock } from '@/blocks/blocks/linkedin'
import { LinkupBlock } from '@/blocks/blocks/linkup'
+import { MondayBlock } from '@/blocks/blocks/monday'
import { MailchimpBlock } from '@/blocks/blocks/mailchimp'
import { MailgunBlock } from '@/blocks/blocks/mailgun'
import { ManualTriggerBlock } from '@/blocks/blocks/manual_trigger'
@@ -215,6 +216,7 @@ export const registry: Record = {
microsoft_planner: MicrosoftPlannerBlock,
microsoft_teams: MicrosoftTeamsBlock,
mistral_parse: MistralParseBlock,
+ monday: MondayBlock,
mongodb: MongoDBBlock,
mysql: MySQLBlock,
neo4j: Neo4jBlock,
diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts
index 572593f677..0c761371f5 100644
--- a/apps/sim/blocks/types.ts
+++ b/apps/sim/blocks/types.ts
@@ -209,6 +209,7 @@ export interface SubBlockConfig {
max?: number
columns?: string[]
placeholder?: string
+ note?: string
password?: boolean
readOnly?: boolean
showCopyButton?: boolean
diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx
index f84923f2b7..17a197897e 100644
--- a/apps/sim/components/icons.tsx
+++ b/apps/sim/components/icons.tsx
@@ -253,6 +253,109 @@ export function MessagesIcon(props: SVGProps) {
)
}
+export function MondayIcon(props: SVGProps) {
+ return (
+
+ )
+}
+
export function NotificationsIcon(props: SVGProps) {
return (