---
title: Workflow Metadata
description: Use Flow for automation and state machines for strict lifecycle transitions.
---

# Workflow Metadata

ObjectStack no longer has a standalone Salesforce-style Workflow Rule authoring
type. Use:

- **Flow** for event-triggered or scheduled automation.
- **State machine metadata** for strict lifecycle transitions.
- **Approval nodes** inside Flow for human approval pauses.

This page keeps the historical route but documents the current split.

---

## Flow for automation

Use Flow when something should happen after a record event, schedule, button, or
subflow call:

```typescript
import type { Flow } from '@objectstack/spec/automation';

export const dealClosedWon: Flow = {
  name: 'deal_closed_won',
  label: 'Deal Closed Won',
  type: 'record_change',
  status: 'active',
  trigger: {
    objectName: 'opportunity',
    operations: ['update'],
    condition: "record.stage == 'closed_won' && previous.stage != 'closed_won'",
  },
  nodes: [
    { id: 'start', type: 'start', label: 'Start' },
    { id: 'set_closed_date', type: 'update_record', label: 'Set Closed Date' },
    { id: 'notify_sales', type: 'notify', label: 'Notify Sales' },
    { id: 'end', type: 'end', label: 'End' },
  ],
  edges: [
    { id: 'e1', source: 'start', target: 'set_closed_date' },
    { id: 'e2', source: 'set_closed_date', target: 'notify_sales' },
    { id: 'e3', source: 'notify_sales', target: 'end' },
  ],
};
```

Flow node types are open-ended: the schema accepts built-in node ids and
plugin-registered node ids. The live automation engine validates executors at
registration/runtime.

---

## State machines for lifecycle constraints

Use `StateMachineSchema` when the core requirement is "this object can only move
through these states by these events."

```typescript
import type { StateMachineConfig } from '@objectstack/spec/automation';

export const caseLifecycle: StateMachineConfig = {
  id: 'case_lifecycle',
  initial: 'new',
  states: {
    new: {
      on: {
        ASSIGN: { target: 'assigned' },
      },
    },
    assigned: {
      on: {
        RESOLVE: { target: 'resolved', cond: 'has_resolution' },
        ESCALATE: { target: 'escalated' },
      },
    },
    escalated: {
      on: {
        RESOLVE: { target: 'resolved', cond: 'has_resolution' },
      },
    },
    resolved: {
      type: 'final',
    },
  },
};
```

State machines describe valid transitions and guards. Use Flow nodes for side
effects around those transitions when you need notifications, record updates, or
external calls.

---

## Approvals

Approvals are Flow nodes:

```typescript
{
  id: 'manager_approval',
  type: 'approval',
  label: 'Manager Approval',
  config: {
    approvers: [{ type: 'user', value: '${record.owner_manager_id}' }],
    behavior: 'all',
    approvalStatusField: 'approval_status',
    lockRecord: true,
  },
}
```

The approvals plugin persists `sys_approval_request` and
`sys_approval_action`, enforces record locks, mirrors the optional status field,
and resumes the paused Flow after approve/reject.

---

## Migration

| Old concept | Current equivalent |
|:---|:---|
| Workflow Rule | Flow |
| Time trigger | Scheduled Flow |
| Field update action | `update_record` node |
| Email alert | `notify` node |
| HTTP call | `http` node |
| Approval Process | Flow with one or more `approval` nodes |

If the old rule was "when X happens, do Y", model it as a small Flow. If the old
rule was "this record must move through controlled states", model the lifecycle
as a state machine and use Flow for side effects.
