Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/sim/blocks/blocks/webflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1858,7 +1858,7 @@ export const auth = betterAuth({
authorizationUrl: 'https://webflow.com/oauth/authorize',
tokenUrl: 'https://api.webflow.com/oauth/access_token',
userInfoUrl: 'https://api.webflow.com/v2/token/introspect',
scopes: ['sites:read', 'sites:write', 'cms:read', 'cms:write'],
scopes: ['sites:read', 'sites:write', 'cms:read', 'cms:write', 'forms:read'],
responseType: 'code',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/webflow`,
getUserInfo: async (tokens) => {
Expand Down
14 changes: 14 additions & 0 deletions apps/sim/lib/webhooks/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
15 changes: 4 additions & 11 deletions apps/sim/lib/webhooks/provider-subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,7 +1400,7 @@ export async function createWebflowWebhookSubscription(
): Promise<string | undefined> {
try {
const { path, providerConfig } = webhookData
const { siteId, triggerId, collectionId, formId } = providerConfig || {}
const { siteId, triggerId, collectionId, formName } = providerConfig || {}

if (!siteId) {
webflowLogger.warn(`[${requestId}] Missing siteId for Webflow webhook creation.`, {
Expand Down Expand Up @@ -1455,17 +1455,10 @@ export async function createWebflowWebhookSubscription(
url: notificationUrl,
}

if (collectionId && webflowTriggerType.startsWith('collection_item_')) {
// Note: Webflow API only supports 'filter' for form_submission triggers.
if (formName && webflowTriggerType === 'form_submission') {
requestBody.filter = {
resource_type: 'collection',
resource_id: collectionId,
}
}

if (formId && webflowTriggerType === 'form_submission') {
requestBody.filter = {
resource_type: 'form',
resource_id: formId,
name: formName,
}
}

Expand Down
38 changes: 31 additions & 7 deletions apps/sim/lib/webhooks/utils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,15 +800,39 @@ export async function formatWebhookInput(
}

if (foundWebhook.provider === 'webflow') {
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
const triggerId = providerConfig.triggerId as string | undefined

// Form submission trigger
if (triggerId === 'webflow_form_submission') {
return {
siteId: body?.siteId || '',
formId: body?.formId || '',
name: body?.name || '',
id: body?.id || '',
submittedAt: body?.submittedAt || '',
data: body?.data || {},
schema: body?.schema || {},
formElementId: body?.formElementId || '',
}
}

// Collection item triggers (created, changed, deleted)
// Webflow uses _cid for collection ID and _id for item ID
const { _cid, _id, ...itemFields } = body || {}
return {
siteId: body?.siteId || '',
formId: body?.formId || '',
name: body?.name || '',
id: body?.id || '',
submittedAt: body?.submittedAt || '',
data: body?.data || {},
schema: body?.schema || {},
formElementId: body?.formElementId || '',
collectionId: _cid || body?.collectionId || '',
payload: {
id: _id || '',
cmsLocaleId: itemFields?.cmsLocaleId || '',
lastPublished: itemFields?.lastPublished || itemFields?.['last-published'] || '',
lastUpdated: itemFields?.lastUpdated || itemFields?.['last-updated'] || '',
createdOn: itemFields?.createdOn || itemFields?.['created-on'] || '',
isArchived: itemFields?.isArchived || itemFields?._archived || false,
isDraft: itemFields?.isDraft || itemFields?._draft || false,
fieldData: itemFields,
},
}
}

Expand Down
116 changes: 113 additions & 3 deletions apps/sim/triggers/webflow/collection_item_changed.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -38,6 +42,58 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_changed',
},
fetchOptions: async (blockId: string, _subBlockId: 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',
Expand All @@ -52,6 +108,60 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
field: 'selectedTriggerId',
value: 'webflow_collection_item_changed',
},
fetchOptions: async (blockId: string, _subBlockId: 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',
Expand All @@ -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.',
]
Expand Down
Loading