Skip to content

Custom actions overview

Custom actions are short pieces of JavaScript that run inside a Flow at a moment you choose. Drop them in for any logic the built-in blocks don't cover — calling your own risk API, injecting a claim, tagging a user, blocking a disposable email signup.

Every Flow has Run Custom Action blocks you can place inside any Stage. Each one names one of your saved actions. When the engine reaches that block, it runs your action's code in a secure sandbox, captures whatever the code wrote to the step state or the access token, then continues to the next block as normal.

Your action does not interrupt the user's experience. From the user's perspective, the flow continues just as it would without the action.

Login Flow
Stage: Post-Auth
Block: Risk Evaluate → writes step.risk-evaluate.score
Block: Run Custom Action → your code runs here
└─ action: "cymmetri-fraud-check"
reads: event.user.email, step.risk-evaluate.score
writes: step.cymmetri-fraud-check.tier (via api.state.set)
Block: Decision → branches on step.cymmetri-fraud-check.tier

The three things your action can read and the two it can write are the whole contract.

Your action receives two global objects: event and step.

event is a read-only snapshot of everything the platform knows at the moment your action runs — the user's identity, the request's IP and country, the device fingerprint, the OAuth client, the tenant, the in-flight transaction. The platform populates it before your code starts; you cannot modify it.

step is a read-only map of every earlier block's output in the same flow run. If a Risk Evaluate block ran before yours, its output lives at step['risk-evaluate']. You can read it; you cannot write to it directly (use api.state.set to write your own outputs under your action's slug instead).

A third global, api, is the action's side-effect channel — covered in the next section.

Your action interacts with the flow exclusively through the api object. Six methods cover everything:

MethodWhat it does
api.deny(reason)Terminates the flow. The reason goes to the audit log — it is NOT shown to the end-user.
api.state.set(key, value)Writes to step.<your-action-slug>.<key>. Downstream blocks and Decision expressions can read it.
api.log(level, message)Appends a structured log entry. Visible in the Test pane and the audit stream. console.log and its siblings are aliases.
api.user.setAppMetadata(patch)Shallow-merges patch into the user's app_metadata — the admin-controlled metadata bag. Persists across flows.
api.session.setCustomClaim(name, value)Embeds a custom claim in the access token. The application receives it in the JWT; the user does not see it.
api.fetch(url, init?)Makes an outbound HTTPS request. Only hostnames on your fetch allowlist are reachable. Returns { status, headers, body }.

Actions run in an isolated JavaScript environment — not a Node.js process. The sandbox gives your code a clean V8 isolate with access to the event, step, and api globals only. Nothing leaks in from the host.

Practical constraints:

  • 256 KB maximum code size per action. Minify when needed; keep logic focused.
  • 16 MB heap per invocation. Sufficient for JSON manipulation; not for large in-memory datasets.
  • Configurable timeout set in your runtime profile. Actions that exceed the timeout are treated as a deny.
  • Outbound HTTP is SSRF-guarded. The runtime enforces an allowlist before any packet leaves the pod. Unlisted hosts return an immediate error.
  • No Node APIs. require, process, fs, and the file system are not available. Use api.fetch for outbound calls and api.log for structured output.

These limits exist for two reasons: latency and safety. Every action runs synchronously in the auth path — a slow or runaway action delays the user's login. The sandbox ensures a misbehaving action cannot read credentials, exhaust host memory, or reach internal infrastructure.

Use a built-in block first. Built-in blocks are audited, maintained, and accountable. Reach for a custom action when nothing built-in fits your need.

Custom actions are the right tool when you need to:

  • Call your own risk API or fraud-scoring service and branch on the result.
  • Inject a custom claim into the access token that your application reads at runtime.
  • Tag users with derived properties (tier, account_type, last_seen_country) that survive across sessions via app_metadata.
  • Apply business rules that change per-tenant — an allowlist of corporate email domains, a regional compliance check, a custom MFA bypass condition.
  • Block a disposable email signup or an ASN you've flagged internally.

Custom actions are the wrong tool when you need to:

  • Run long-running background work. Use webhook subscriptions for that.
  • Handle application business logic that belongs in your own service layer.
  • Access a user's password or factor secrets. They are never exposed to actions.
  • Carry state across separate flow runs. Each action invocation is stateless — use app_metadata if you need per-user state that persists.

Navigate to Flows in the left sidebar, then select the Custom Actions tab. From there you can create, edit, enable, disable, and delete actions. You can also reach an action directly from inside the flow editor: add a Run Custom Action block to any Stage, then open the action picker inside that block.

New to custom actions? Your first custom action walks you through building and attaching one in ten minutes.