windmill-labs/windmill · error · Error

This function can only be called as a flow step

Error message

This function can only be called as a flow step

What it means

windmill's TypeScript client throws this when an approval-related helper in client.ts is invoked without the WM_FLOW_STEP_ID environment variable set. The function works only as a step inside a running flow, where Windmill injects that variable to identify the current step. Outside a flow (standalone script, preview) there is no step to attach the approval to, so the call cannot proceed.

Source

Thrown at typescript-client/client.ts:1361

  message,
  approver,
  defaultArgsJson,
  dynamicEnumsJson,
  resumeButtonText,
  cancelButtonText,
}: SlackApprovalOptions): Promise<void> {
  const workspace = getWorkspace();
  const flowJobId = getEnv("WM_FLOW_JOB_ID");

  if (!flowJobId) {
    throw new Error(
      "You can't use this function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
    );
  }

  const flowStepId = getEnv("WM_FLOW_STEP_ID");
  if (!flowStepId) {
    throw new Error("This function can only be called as a flow step");
  }

  // Only include non-empty parameters
  const params: {
    approver?: string;
    message?: string;
    slackResourcePath: string;
    channelId: string;
    flowStepId: string;
    defaultArgsJson?: string;
    dynamicEnumsJson?: string;
    resumeButtonText?: string;
    cancelButtonText?: string;
  } = {
    slackResourcePath,
    channelId,
    flowStepId,
  };

View on GitHub (pinned to e474e8803c)

Solutions

  1. Run the code as a step inside a flow (add the script to a flow in the Windmill UI) so WM_FLOW_STEP_ID is injected.
  2. If testing, guard the call: `if (!getEnv("WM_FLOW_STEP_ID")) return;` or stub the env for the test.
  3. Use the flow preview mode of the Windmill UI for development instead of executing the script standalone.
  4. Verify your worker/runner does not sanitize the environment and remove WM_FLOW_STEP_ID.

Example fix

// before
await teamsApproval({ approver: 'admin@corp.com', message: 'ok' }); // standalone -> throws
// after
if (getEnv('WM_FLOW_STEP_ID')) {
  await teamsApproval({ approver: 'admin@corp.com', message: 'ok' });
} else {
  console.log('skipped: not running as a flow step');
}
Defensive patterns

Strategy: validation

Validate before calling

import { getEnv } from 'windmill-client';
export const isFlowStep = () => Boolean(getEnv('WM_FLOW_STEP_ID'));

Type guard

function requireFlowStepId(): string {
  const id = getEnv('WM_FLOW_STEP_ID');
  if (!id) throw new Error('not running as a flow step');
  return id;
}

Try / catch

try {
  await approvalFn(opts);
} catch (e) {
  if (e.message === 'This function can only be called as a flow step') {
    // skip or fallback in standalone runs
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the approval function (typescript-client/client.ts:1361) from a standalone script run, a CLI execution, or any environment where getEnv("WM_FLOW_STEP_ID") returns empty.

Common situations: Testing approval code locally with `wmill script run` or `deno run`; running the script as a top-level script instead of a flow step; sandboxed or custom-runner environments that strip Windmill-injected env vars.

Related errors


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