Compare commits

...

2 Commits

Author SHA1 Message Date
waleed
a3d529dea4 added form submission trigger to webflow 2026-01-15 20:15:20 -08:00
waleed
9336ea7082 fix(webflow): fix collection & site dropdown in webflow triggers 2026-01-15 20:07:25 -08:00
7 changed files with 433 additions and 17 deletions

View File

@@ -127,6 +127,7 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
...getTrigger('webflow_collection_item_created').subBlocks,
...getTrigger('webflow_collection_item_changed').subBlocks,
...getTrigger('webflow_collection_item_deleted').subBlocks,
...getTrigger('webflow_form_submission').subBlocks,
],
tools: {
access: [

View File

@@ -251,6 +251,20 @@ export function shouldSkipWebhookEvent(webhook: any, body: any, requestId: strin
}
}
// Webflow collection filtering - filter by collectionId if configured
if (webhook.provider === 'webflow') {
const configuredCollectionId = providerConfig.collectionId
if (configuredCollectionId) {
const payloadCollectionId = body?.payload?.collectionId || body?.collectionId
if (payloadCollectionId && payloadCollectionId !== configuredCollectionId) {
logger.info(
`[${requestId}] Webflow collection '${payloadCollectionId}' doesn't match configured collection '${configuredCollectionId}' for webhook ${webhook.id}, skipping`
)
return true
}
}
}
return false
}

View File

@@ -1455,13 +1455,7 @@ export async function createWebflowWebhookSubscription(
url: notificationUrl,
}
if (collectionId && webflowTriggerType.startsWith('collection_item_')) {
requestBody.filter = {
resource_type: 'collection',
resource_id: collectionId,
}
}
// Note: Webflow API only supports 'filter' for form_submission triggers.
if (formId && webflowTriggerType === 'form_submission') {
requestBody.filter = {
resource_type: 'form',

View File

@@ -1,6 +1,10 @@
import { createLogger } from '@sim/logger'
import { WebflowIcon } from '@/components/icons'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { TriggerConfig } from '../types'
const logger = createLogger('webflow-collection-item-changed-trigger')
export const webflowCollectionItemChangedTrigger: TriggerConfig = {
id: 'webflow_collection_item_changed',
name: 'Collection Item Changed',
@@ -38,6 +42,58 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_changed',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) {
throw new Error('No Webflow credential selected')
}
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow sites')
}
const data = await response.json()
if (data.sites && Array.isArray(data.sites)) {
return data.sites.map((site: { id: string; name: string }) => ({
id: site.id,
label: site.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow sites:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) return null
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId: optionId }),
})
if (!response.ok) return null
const data = await response.json()
const site = data.sites?.find((s: { id: string }) => s.id === optionId)
if (site) {
return { id: site.id, label: site.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials'],
},
{
id: 'collectionId',
@@ -52,6 +108,60 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_changed',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null
if (!credentialId || !siteId) {
return []
}
try {
const response = await fetch('/api/tools/webflow/collections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow collections')
}
const data = await response.json()
if (data.collections && Array.isArray(data.collections)) {
return data.collections.map((collection: { id: string; name: string }) => ({
id: collection.id,
label: collection.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow collections:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null
if (!credentialId || !siteId) return null
try {
const response = await fetch('/api/tools/webflow/collections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId }),
})
if (!response.ok) return null
const data = await response.json()
const collection = data.collections?.find((c: { id: string }) => c.id === optionId)
if (collection) {
return { id: collection.id, label: collection.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials', 'siteId'],
},
{
id: 'triggerSave',
@@ -72,9 +182,9 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
type: 'text',
defaultValue: [
'Connect your Webflow account using the "Select Webflow credential" button above.',
'Enter your Webflow Site ID (found in the site URL or site settings).',
'Optionally enter a Collection ID to monitor only specific collections.',
'If no Collection ID is provided, the trigger will fire for items changed in any collection on the site.',
'Select your Webflow site from the dropdown.',
'Optionally select a collection to monitor only specific collections.',
'If no collection is selected, the trigger will fire for items changed in any collection on the site.',
'The webhook will trigger whenever an existing item is updated in the specified collection(s).',
'Make sure your Webflow account has appropriate permissions for the specified site.',
]

View File

@@ -1,6 +1,10 @@
import { createLogger } from '@sim/logger'
import { WebflowIcon } from '@/components/icons'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { TriggerConfig } from '../types'
const logger = createLogger('webflow-collection-item-created-trigger')
export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
id: 'webflow_collection_item_created',
name: 'Collection Item Created',
@@ -20,6 +24,7 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
{ label: 'Collection Item Created', id: 'webflow_collection_item_created' },
{ label: 'Collection Item Changed', id: 'webflow_collection_item_changed' },
{ label: 'Collection Item Deleted', id: 'webflow_collection_item_deleted' },
{ label: 'Form Submission', id: 'webflow_form_submission' },
],
value: () => 'webflow_collection_item_created',
required: true,
@@ -51,6 +56,58 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_created',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) {
throw new Error('No Webflow credential selected')
}
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow sites')
}
const data = await response.json()
if (data.sites && Array.isArray(data.sites)) {
return data.sites.map((site: { id: string; name: string }) => ({
id: site.id,
label: site.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow sites:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) return null
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId: optionId }),
})
if (!response.ok) return null
const data = await response.json()
const site = data.sites?.find((s: { id: string }) => s.id === optionId)
if (site) {
return { id: site.id, label: site.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials'],
},
{
id: 'collectionId',
@@ -65,6 +122,60 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_created',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null
if (!credentialId || !siteId) {
return []
}
try {
const response = await fetch('/api/tools/webflow/collections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow collections')
}
const data = await response.json()
if (data.collections && Array.isArray(data.collections)) {
return data.collections.map((collection: { id: string; name: string }) => ({
id: collection.id,
label: collection.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow collections:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null
if (!credentialId || !siteId) return null
try {
const response = await fetch('/api/tools/webflow/collections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId }),
})
if (!response.ok) return null
const data = await response.json()
const collection = data.collections?.find((c: { id: string }) => c.id === optionId)
if (collection) {
return { id: collection.id, label: collection.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials', 'siteId'],
},
{
id: 'triggerSave',
@@ -85,9 +196,9 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
type: 'text',
defaultValue: [
'Connect your Webflow account using the "Select Webflow credential" button above.',
'Enter your Webflow Site ID (found in the site URL or site settings).',
'Optionally enter a Collection ID to monitor only specific collections.',
'If no Collection ID is provided, the trigger will fire for items created in any collection on the site.',
'Select your Webflow site from the dropdown.',
'Optionally select a collection to monitor only specific collections.',
'If no collection is selected, the trigger will fire for items created in any collection on the site.',
'The webhook will trigger whenever a new item is created in the specified collection(s).',
'Make sure your Webflow account has appropriate permissions for the specified site.',
]

View File

@@ -1,6 +1,10 @@
import { createLogger } from '@sim/logger'
import { WebflowIcon } from '@/components/icons'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { TriggerConfig } from '../types'
const logger = createLogger('webflow-collection-item-deleted-trigger')
export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
id: 'webflow_collection_item_deleted',
name: 'Collection Item Deleted',
@@ -38,6 +42,58 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_deleted',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) {
throw new Error('No Webflow credential selected')
}
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow sites')
}
const data = await response.json()
if (data.sites && Array.isArray(data.sites)) {
return data.sites.map((site: { id: string; name: string }) => ({
id: site.id,
label: site.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow sites:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) return null
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId: optionId }),
})
if (!response.ok) return null
const data = await response.json()
const site = data.sites?.find((s: { id: string }) => s.id === optionId)
if (site) {
return { id: site.id, label: site.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials'],
},
{
id: 'collectionId',
@@ -52,6 +108,60 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_deleted',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null
if (!credentialId || !siteId) {
return []
}
try {
const response = await fetch('/api/tools/webflow/collections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow collections')
}
const data = await response.json()
if (data.collections && Array.isArray(data.collections)) {
return data.collections.map((collection: { id: string; name: string }) => ({
id: collection.id,
label: collection.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow collections:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null
if (!credentialId || !siteId) return null
try {
const response = await fetch('/api/tools/webflow/collections', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId }),
})
if (!response.ok) return null
const data = await response.json()
const collection = data.collections?.find((c: { id: string }) => c.id === optionId)
if (collection) {
return { id: collection.id, label: collection.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials', 'siteId'],
},
{
id: 'triggerSave',
@@ -72,9 +182,9 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
type: 'text',
defaultValue: [
'Connect your Webflow account using the "Select Webflow credential" button above.',
'Enter your Webflow Site ID (found in the site URL or site settings).',
'Optionally enter a Collection ID to monitor only specific collections.',
'If no Collection ID is provided, the trigger will fire for items deleted in any collection on the site.',
'Select your Webflow site from the dropdown.',
'Optionally select a collection to monitor only specific collections.',
'If no collection is selected, the trigger will fire for items deleted in any collection on the site.',
'The webhook will trigger whenever an item is deleted from the specified collection(s).',
'Note: Once an item is deleted, only minimal information (ID, collection, site) is available.',
'Make sure your Webflow account has appropriate permissions for the specified site.',

View File

@@ -1,6 +1,10 @@
import { createLogger } from '@sim/logger'
import { WebflowIcon } from '@/components/icons'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import type { TriggerConfig } from '../types'
const logger = createLogger('webflow-form-submission-trigger')
export const webflowFormSubmissionTrigger: TriggerConfig = {
id: 'webflow_form_submission',
name: 'Form Submission',
@@ -20,6 +24,10 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
requiredScopes: [],
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'webflow_form_submission',
},
},
{
id: 'siteId',
@@ -30,6 +38,62 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
required: true,
options: [],
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'webflow_form_submission',
},
fetchOptions: async (blockId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) {
throw new Error('No Webflow credential selected')
}
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId }),
})
if (!response.ok) {
throw new Error('Failed to fetch Webflow sites')
}
const data = await response.json()
if (data.sites && Array.isArray(data.sites)) {
return data.sites.map((site: { id: string; name: string }) => ({
id: site.id,
label: site.name,
}))
}
return []
} catch (error) {
logger.error('Error fetching Webflow sites:', error)
throw error
}
},
fetchOptionById: async (blockId: string, _subBlockId: string, optionId: string) => {
const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as
| string
| null
if (!credentialId) return null
try {
const response = await fetch('/api/tools/webflow/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential: credentialId, siteId: optionId }),
})
if (!response.ok) return null
const data = await response.json()
const site = data.sites?.find((s: { id: string }) => s.id === optionId)
if (site) {
return { id: site.id, label: site.name }
}
return null
} catch {
return null
}
},
dependsOn: ['triggerCredentials'],
},
{
id: 'formId',
@@ -39,6 +103,10 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
description: 'The ID of the specific form to monitor (optional - leave empty for all forms)',
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'webflow_form_submission',
},
},
{
id: 'triggerSave',
@@ -47,6 +115,10 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
hideFromPreview: true,
mode: 'trigger',
triggerId: 'webflow_form_submission',
condition: {
field: 'selectedTriggerId',
value: 'webflow_form_submission',
},
},
{
id: 'triggerInstructions',
@@ -55,7 +127,7 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
type: 'text',
defaultValue: [
'Connect your Webflow account using the "Select Webflow credential" button above.',
'Enter your Webflow Site ID (found in the site URL or site settings).',
'Select your Webflow site from the dropdown.',
'Optionally enter a Form ID to monitor only a specific form.',
'If no Form ID is provided, the trigger will fire for any form submission on the site.',
'The webhook will trigger whenever a form is submitted on the specified site.',
@@ -68,6 +140,10 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'webflow_form_submission',
},
},
],