Skip to main content
It is recommended to enable retries for workflow runs to improve reliability. However, in some cases, you may want to stop execution immediately when an error occurs, without causing additional retries. Upstash Workflow provides several mechanisms to terminate workflow execution gracefully.

Using WorkflowNonRetryableError

WorkflowNonRetryableError lets you explicitly fail a workflow without entering the retry cycle. When thrown, the workflow run is marked as failed, which:
  • Triggers the failure function (if defined)
  • Sends the workflow run to the DLQ
TypeScript
export const { POST } = serve<{ topic: string }>(async (context) => {
  const payload = context.requestPayload

  const isExists = await context.run("is-user-exists", () => { ... });

  if (!isExists) {
    throw new WorkflowNonRetryableError("The user does not exists!")
  }
})

Using context.cancel()

You can cancel a workflow run explicitly from inside the workflow. When canceled, the run is labeled as canceled instead of failed. This means:
  • The failure handler will NOT be triggered
  • The workflow will NOT be sent to the DLQ
export const { POST } = serve<{ orderId: string }>(async (context) => {
  const { orderId } = context.requestPayload;

  // Check if order is still valid
  const orderStatus = await context.run("check-order-status", async () => {
    return await getOrderStatus(orderId);
  });

  if (orderStatus === "cancelled") {
    // Stop execution gracefully without error
    await context.cancel();
    return;
  }

  // Continue processing if order is valid
  await context.run("process-order", async () => {
    return await processOrder(orderId);
  });
});

Using conditional execution

You can also use guard conditions to skip certain steps and exit early, without throwing errors or canceling the workflow. In this case, the workflow run completes successfully because no error was raised.
export const { POST } = serve<{ data: any }>(async (context) => {
  const { data } = context.requestPayload;

  // Check if order is still valid
  const orderStatus = await context.run("check-order-status", async () => {
    return await getOrderStatus(orderId);
  });

  if (orderStatus === "not-found") {
    // Stop execution without error
    return;
  }

  // Continue processing if order is valid
  await context.run("process-order", async () => {
    return await processOrder(orderId);
  });
});