windmill-labs/windmill · error · Error

Unexpected trigger kind ${triggerKind}. Allowed kinds are: r

Error message

Unexpected trigger kind ${triggerKind}. Allowed kinds are: routes, kafka, mqtt, postgres, sqs, gcp, websockets, nats, azure, emails, schedules.

What it means

existsTrigger in frontend/src/lib/utils_deployable.ts (line 119) is the central dispatcher that maps a TriggerKind string to the correct `*Service.exists*Trigger` API call. It only recognizes the 12 kinds that map to backend trigger endpoints (routes, kafka, mqtt, amqp, postgres, sqs, gcp, websockets, nats, azure, emails, schedules); the TriggerKind union in frontend/src/lib/components/triggers.ts:44 also contains UI-only kinds ('webhooks', 'default_emails', 'cli', 'scheduledPoll', 'nextcloud', 'google', 'github') which fall through to this throw. Note the message's allowed-kinds list is slightly stale: it omits 'amqp', which the code does handle.

Source

Thrown at frontend/src/lib/utils_deployable.ts:119

	} else if (triggerKind === 'postgres') {
		return await PostgresTriggerService.existsPostgresTrigger(data)
	} else if (triggerKind === 'sqs') {
		return await SqsTriggerService.existsSqsTrigger(data)
	} else if (triggerKind === 'gcp') {
		return await GcpTriggerService.existsGcpTrigger(data)
	} else if (triggerKind === 'websockets') {
		return await WebsocketTriggerService.existsWebsocketTrigger(data)
	} else if (triggerKind === 'nats') {
		return await NatsTriggerService.existsNatsTrigger(data)
	} else if (triggerKind === 'azure') {
		return await AzureTriggerService.existsAzureTrigger(data)
	} else if (triggerKind === 'emails') {
		return await EmailTriggerService.existsEmailTrigger(data)
	} else if (triggerKind === 'schedules') {
		return await ScheduleService.existsSchedule(data)
	}

	throw new Error(
		`Unexpected trigger kind ${triggerKind}. Allowed kinds are: routes, kafka, mqtt, postgres, sqs, gcp, websockets, nats, azure, emails, schedules.`
	)
}

/**
 * Strip operational state (`mode`, `enabled`) from a trigger/schedule payload
 * before sending it to an update endpoint via the merge UI. The backend's
 * `update_trigger` handler preserves the target row's existing `mode` when
 * both fields are absent from the request (`is_mode_unspecified()`), so
 * stripping here lets a fork→parent (or parent→fork) deploy carry config
 * changes without flipping the target's enabled/disabled state. Schedules'
 * `EditSchedule` already lacks `enabled` on the backend, but stripping keeps
 * the intent explicit and matches the YAML/CLI round-trip behavior.
 *
 * Used by the legacy `kind === 'trigger'` path in `utils_workspace_deploy.ts`
 * (the cross-workspace deploy UI). The merge-UI deploy goes through the
 * shared `deployItem` in `windmill-utils-internal`, which applies its own
 * `stripOperationalStateOnUpdate` at the dispatch layer.

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check the actual triggerKind value logged in the error; it must be one of routes, kafka, mqtt, amqp, postgres, sqs, gcp, websockets, nats, azure, emails, schedules — fix the caller to pass one of those (e.g. 'schedules' not 'schedule', 'routes' not 'http').
  2. If the kind is a UI-only kind ('webhooks', 'cli', 'scheduledPoll', 'nextcloud', 'google', 'github', 'default_emails'), do not route it through existsTrigger; handle it before the call or add an exists check branch mapping it to the right service.
  3. If you added a new trigger kind, add a matching `else if` branch in existsTrigger wiring it to its Service's exists endpoint, and update the allowed-kinds list in the error message.
  4. If the kind arrives from the backend compareWorkspaces output (e.g. 'http_trigger', 'kafka_trigger'), convert it with the existing kind-mapping helpers before calling existsTrigger.

Example fix

// before
await existsTrigger({ workspace, path }, 'schedule')
// after
await existsTrigger({ workspace, path }, 'schedules')
Defensive patterns

Strategy: validation

Validate before calling

const EXISTS_KINDS = ['routes','kafka','mqtt','amqp','postgres','sqs','gcp','websockets','nats','azure','emails','schedules'] as const
if (!EXISTS_KINDS.includes(triggerKind as any)) {
  throw new Error(`Skipping exists check: unsupported trigger kind ${triggerKind}`)
}
await existsTrigger({ workspace, path }, triggerKind)

Type guard

const EXISTS_KINDS = ['routes','kafka','mqtt','amqp','postgres','sqs','gcp','websockets','nats','azure','emails','schedules'] as const
type ExistsKind = (typeof EXISTS_KINDS)[number]
function isExistsKind(k: string): k is ExistsKind {
  return (EXISTS_KINDS as readonly string[]).includes(k)
}

Try / catch

try {
  const exists = await existsTrigger({ workspace, path }, triggerKind)
} catch (e) {
  if (String(e?.message).startsWith('Unexpected trigger kind')) {
    console.warn(`No exists-check for kind ${triggerKind}; treating as not-existing`)
  } else throw e
}

Prevention

When it happens

Trigger: Calling existsTrigger(data, triggerKind) with any TriggerKind not in the if-chain — concretely 'webhooks', 'default_emails', 'cli', 'scheduledPoll', 'nextcloud', 'google', or 'github' — or a typo'd/renamed kind like 'schedule' instead of 'schedules' or 'http' instead of 'routes'. Called from alreadyExists and checkItemExists during workspace deploy checks.

Common situations: A developer adds a new trigger type (or UI-only trigger kind) to the TriggerKind union and to a page/feature that lists triggers, but forgets to add a branch in existsTrigger; code that was typed against a narrower kind union is fed a kind coming from the backend's compareWorkspaces output or a user-supplied query param; renaming a kind string in one module but not the dispatcher.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/0da45073dcaf609a. Report an issue: GitHub.