Skip to content

Create an action

If a platform template doesn't cover your case, write a custom Action. Three options for how:

  • In-console editor — TypeScript editor inside the admin console. Best for short Actions and rapid iteration.
  • External package — author locally with your editor of choice, package as a tarball, upload via the console. Best for substantial Actions with their own tests.
  • CLI scaffoldpnpm dlx @intelliauth/action-cli init to scaffold a project; upload to publish. Best for actions in CI/CD.

This guide walks the in-console editor. The other paths produce identical Actions; the workflow is just different.

Authentication → Actions → New action.

Pick a starter:

  • Empty — bare-bones skeleton.
  • Domain allowlist — pre-filled with the "reject sign-ins outside a domain list" pattern.
  • Webhook emit — pre-filled with the "post to a URL" pattern.
  • Custom claim — pre-filled with the "add a claim to the token" pattern.

Picking a starter copies its code as a baseline; you edit from there.

Top of the editor — metadata panel:

  • Name — what tenant admins see in the Actions list ("Block by email domain").
  • Slug — URL-safe identifier ("block-by-email-domain"). Immutable once saved.
  • Version — semver string ("0.1.0"). Auto-bumps on save; you can override.
  • Compatible triggers — which flow trigger slots this Action can attach to (e.g., login.pre-credential-check, registration.pre-create). Multi-select.
  • Config schema — the form tenant admins fill when attaching this Action to a flow. Defined as JSON Schema (the editor has a visual schema-builder for non-developers).

The code editor is the bulk of the page. Default starting skeleton:

export async function execute(event, api) {
// event.* — read-only context: user, request IP/country/VPN flags, OAuth client, etc.
// api.* — side effects: api.access.deny(), api.accessToken.setCustomClaim(), api.fetch()
// Use the {} picker in the editor to browse every available event.* path.
return api.access.allow()
}

The full programming model (event shape, api methods, sandbox constraints) is in the Custom actions overview and the API reference. For most Actions you'll only use a small slice of it.

The editor has a Test tab. Fill in synthetic inputs (a fake user, a fake request, a fake config); click Run; see the output. Useful for confirming the logic works before pushing to live flows.

Click Save. The Action is published at the version you set. Available in the Actions list immediately, and attachable via the Flow builder.

The publish action records flow.action_published in audit.

Now the part that makes it run. Authentication → Flows → pick a flow → trigger slot → Add Action → pick yours → configure → save.

The new Action runs on the NEXT flow execution (next sign-in / signup / etc., depending on the flow).

export async function execute(event, api) {
const email = event.user?.email
if (!email) return api.access.allow() // social sign-in: skip
const domain = email.split('@')[1]?.toLowerCase()
const allowed = event.client.metadata?.allowed_domains ?? []
if (allowed.includes(domain)) {
return api.access.allow()
}
return api.access.deny(
`Sign-in restricted. Use an email at one of: ${allowed.join(', ')}.`
)
}

Attach to the Pre-Login Stage of the Login flow.

export async function execute(event, api) {
const { id, email, name } = event.user
await api.fetch(event.client.metadata.webhook_url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: 'user.signed_up',
user_id: id,
email,
name,
}),
})
return api.access.allow()
}

Attach to the Post-Create Stage of the Registration flow.

export async function execute(event, api) {
const tier = event.user.app_metadata?.tier ?? 'free'
api.accessToken.setCustomClaim('https://cymmetri.com/tier', tier)
return api.access.allow()
}

Attach to the Post-Login Stage of the Login flow. The claim ends up on the issued access token.

  • Keep Actions short. Per-Action time budget is a few seconds. Slow Actions slow sign-in for every user.
  • Don't call slow external APIs synchronously. If you must, set a short timeout + fail-open (return continue on timeout, log the failure).
  • Test before attaching. The flow-builder test feature lets you simulate a sign-in through the flow with your Action attached. Use it.
  • Version intentionally. Don't ship v0.1.0 → v0.1.1 with breaking config schema changes; the change in schema breaks every attached flow.
  • Log freely. console.log and console.error output is captured per-run and visible in the audit + flow-run detail pages. Cheap observability.