The Test tab is the fastest path from "I wrote some code" to "I know it works." You mock the event, shape the upstream step state, click Run, and read a structured result — all without touching a live Flow or a real user session.
/screenshots/admin.flows.custom_actions.testing/full-pane.png The five sections of the Test tab
Section titled “The five sections of the Test tab”A test run has four inputs and one output panel. The diagram at the top of the tab stitches them together: Preset fills Event JSON, Event JSON and Step state flow into your handler as event and step, and the sandbox writes everything your code did into the result tabs.
Preset
Section titled “Preset”A preset is a one-click shortcut that fills the Event JSON editor with a realistic request shape so you do not have to hand-craft one from scratch. Choosing a preset overwrites whatever is currently in Event JSON. The Custom preset leaves Event JSON empty and editable for scenarios the built-in shapes do not cover.
The five presets are:
| Preset | What it sets | When to reach for it |
|---|---|---|
| Logged-in US user | event.user populated, event.request.geo.country = US, no risk flags | Sanity-check the happy path |
| Tor exit node attempt | event.request.asn.is_tor = true | Verify deny or step-up branches |
| VPN attempt | event.request.asn.is_vpn = true | Test risk-routing logic |
| M2M client credentials | event.client.type = m2m, no user object | Confirm your action handles a missing user gracefully |
| Custom | Empty object {} | Hand-crafted or edge-case scenarios |
An inline-help bubble sits next to the Preset label. It surfaces the same guidance in context while you work.
Event JSON
Section titled “Event JSON”Whatever you type in the Event JSON editor becomes the event parameter your handler function receives. The editor is Monaco-backed with JSON syntax highlighting and theme-syncing — it looks the same in light and dark modes.
The schema mirrors production exactly. If your code branches on event.request.asn.is_tor, set it to true here and the deny path fires on the next run. If you edit Event JSON directly after picking a preset, the preset selector switches to Custom automatically.
Malformed JSON shows a red inline marker and disables the Run button until the syntax is fixed. Invalid event shapes — a field that does not exist in the real schema, for example — do not show a warning; the test runner accepts whatever JSON you provide, exactly as the live runtime would accept whatever state the platform populated at that point in a real Flow.
For field discovery, open your action code and type event. in the Monaco editor — IntelliSense offers the full attribute surface the sandbox exposes.
Step state
Section titled “Step state”The Step state editor populates the step variable inside your action. It simulates the outputs of blocks that ran earlier in the same Stage — the things your action would normally read via step.<block-slug>.<field>.
Key each entry by the upstream block's slug:
{ "password-check": { "verified": true }, "fraud-signal": { "body": { "score": 0.62 } }}Inside your code, step["password-check"].verified reads as true and step["fraud-signal"].body.score reads as 0.62. If your action is the first block in its Stage and reads nothing from earlier blocks, leave this as {}.
An inline-help bubble next to the Step state label explains the key-by-slug pattern in context.
Click Run to boot a fresh isolate and call your handler with the event and step state you configured. The isolate runs under the same limits as production: a 16 MB memory cap and a 5-second time limit. If your action finishes within those bounds, the result panel appears below.
The Run button is disabled when either JSON editor contains invalid syntax.
Tip: the fastest iteration loop is to edit your code in the editor, save with Cmd-S or Ctrl-S, then click Run in the Test tab. The save-and-run cycle takes under two seconds — faster than enabling the action, attaching it to a Flow, and walking a real login.
An inline-help bubble next to Run describes the sandbox contract in detail.
Result tabs
Section titled “Result tabs”After a run, four tabs appear. Each one captures a different dimension of what your code did.
Console
Every api.log call and every console.log, console.warn, console.error, or console.debug call your action made. Each entry shows its level (debug, info, warn, error) as a coloured badge alongside the message text. If your code emitted nothing, the tab reads "No log output." This is the right tab to open first when a run produces an unexpected result.
State writes
Every api.state.set(key, value) call, displayed as a two-column table of key and JSON-formatted value. The keys follow the step.<your-block-slug>.<field> convention, so you can verify exactly what a downstream block would read. If your action made no state writes, the tab reads "No state writes."
API calls
Every other api.* call your action made — api.deny, api.user.setAppMetadata, api.session.setCustomClaim, api.fetch — in call order, with the arguments and return value for each. This tab is the place to confirm that api.deny was called with the right reason string, or that api.fetch returned the response you expected from your allowlisted endpoint.
Result
On success, the Result tab shows a summary: elapsed time in milliseconds, peak memory in bytes, the count of API calls, state writes, and log entries. On error, it shows the error message and, when the sandbox can identify the failing location, a "Jump to line N, col N" link that moves the cursor in the code editor to the exact line.
/screenshots/admin.flows.custom_actions.testing/error-tab.png Side effects are real
Section titled “Side effects are real”The Test tab invokes your action code in the same sandbox the live engine uses. That means side effects execute: api.user.setAppMetadata patches the user record, api.session.setCustomClaim affects the session, and api.fetch makes a real outbound request to your allowlisted endpoint. Run tests against scratch identities; do not use the Test tab against production users.
The platform has no global dry-run flag for actions. If your action must mutate data, create a dedicated test identity in your tenant and test against that.
Outbound fetch and the allowlist
Section titled “Outbound fetch and the allowlist”Outbound api.fetch calls are subject to your tenant's hostname allowlist, exactly as they are in production. If you call a hostname that is not on the allowlist, the call fails in the Test tab with the same error it would produce in a real Flow. This is intentional — the Test tab surfaces allowlist misses before you attach the action to anything.
Secrets handling
Section titled “Secrets handling”If the Event JSON you submit contains a secrets key, the browser replaces its value with { "_test_mode": "<test-secret>" } before the request leaves your browser. This is defence-in-depth: the backend enforces the same redaction on its side, but the client-side step ensures a real secret value never appears in the structured result log where you might inadvertently copy it. Treat the Test tab as if the values you type leave your browser — do not paste production secrets into Event JSON to test a branch; model the shape instead.
When secrets sanitisation fires, a notice appears below the Run button confirming that production secrets were replaced with placeholders.
Rate limits
Section titled “Rate limits”The Test tab enforces a per-action rate limit on test invocations. If you exceed it, an inline notice appears with the number of seconds until you can run again. The limit resets automatically; you do not need to reload the page.
Iterating quickly
Section titled “Iterating quickly”The edit-save-run cycle is the intended workflow. Keep the Test tab open while you edit code. Save the action, switch to the Test tab, click Run, and read the result — then repeat. Because each run boots a fresh isolate, there is no state bleed between runs: every run sees exactly the Event JSON and Step state you configured, nothing more.
For complex actions that branch heavily, work through the presets in order. Start with Logged-in US user to confirm the happy path, then progress to Tor exit node attempt and VPN attempt to verify your risk branches, and finally switch to Custom to test any edge cases specific to your logic.