An Action is a reusable piece of custom logic that runs at a specific trigger point inside a Flow. Two flavours:
Platform-shipped templates — pre-built Actions for common needs. Drop in; configure via form; save.
Custom Actions — TypeScript / JavaScript you author yourself. Upload; configure schema; attach.
The Actions list
Authentication → Actions. Lists every Action available in your tenant.
Each row:
Name — the Action's name.
Kind —
template(platform-shipped) orcustom(yours).Compatible triggers — the trigger slots this Action can be attached to.
Attached — how many flow slots currently use this Action.
Platform templates
The platform ships these out of the box. The list grows over releases:
domain-allowlist — block sign-ins from emails outside an allowed list.
domain-blocklist — block sign-ins from emails on a denied list.
rate-limit-by-ip — refuse requests above a per-IP threshold.
rate-limit-by-user — refuse repeated attempts on the same user account.
decorate-token — add a custom claim to the issued token.
webhook-emit — POST to a URL when this slot fires.
slack-notify — post to a Slack incoming webhook (a specialised webhook-emit).
block-by-risk-score — block when the risk engine's score exceeds a threshold.
demand-mfa-by-risk-score — force MFA even when policy wouldn't normally.
redirect-by-group — redirect specific group members to specific URLs after sign-in.
Each template ships with a configuration form. No code; just fill in fields.
Custom Actions
When templates don't cover your case, write your own. The flow:
Open Actions → New action.
Pick a starter template (or "Empty Action" for fully custom).
The editor opens with a TypeScript skeleton.
Write the logic.
Configure the Action's metadata (name, slug, compatible triggers, config schema).
Save. The Action is now available to attach to flow slots.
The full programming model + the platform's runtime sandbox is documented in the developer-facing pipelines concept.
What an Action looks like (minimal)
// src/index.ts in the Action package
export async function execute(input) {
// input: { trigger, user, request, state, context }
// return: { kind: 'continue' | 'block' | 'demand_mfa' | 'redirect' }
return { kind: 'continue' }
}
The skeleton is filled in by the editor; you customise the logic.
Versioning
Each Action has a version field on its metadata. When you edit + save, the version bumps. Flows reference Actions by version — a Flow attached to domain-allowlist v1.0.0 keeps running v1.0.0 even after you publish v1.1.0.
To roll out a new version to an attached flow:
Publish the new version (v1.1.0).
Open the flow.
Update the version pinned to the slot.
Save the flow.
The flow now runs v1.1.0. Existing in-flight sign-ins continue on whatever version started them; new sign-ins use the new version.
This pattern is the cheap insurance against "the Action update broke production for 10 minutes". You stage new versions; you roll out per-flow when you've verified the staging.
Visibility
Actions are tenant-scoped. The Actions in tenant A's catalogue are not visible to tenant B. There's no cross-tenant Action sharing today.
If you have many tenants running the same Action, you'll author it in each — or use the management API to script the upload across tenants.
Audit
Every Action execution + every Action edit lands in audit:
flow.action_attached— Action added to a flow slot.flow.action_detached— removed.flow.action_updated— config changed.flow.action_executed— ran during a flow.flow.action_published— new version published.
The combination lets you reconstruct "why did sign-ins start failing yesterday at 14:00?" — usually a flow.action_attached or flow.action_updated entry near that timestamp.