ObjectStackObjectStack

Workflow rules — declarative record logic

Use Flow for automation and state machines for strict lifecycle transitions.

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

  • Flow for event-triggered or scheduled automation.
  • A state_machine validation rule 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:

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

export const dealClosedWon: Flow = {
  name: 'deal_closed_won',
  label: 'Deal Closed Won',
  type: 'record_change',
  status: 'active',
  nodes: [
    {
      id: 'start',
      type: 'start',
      label: 'Start',
      config: {
        triggerType: 'record-after-update',
        objectName: 'opportunity',
        condition: "record.stage == 'closed_won' && previous.stage != 'closed_won'",
      },
    },
    { 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 a state_machine validation rule when the core requirement is "this record can only move through these states." It is one of the object's validations: a flat table of each state's allowed next states, enforced by the write path (see State Machine).

import { ObjectSchema, Field } from '@objectstack/spec/data';

export const SupportCase = ObjectSchema.create({
  name: 'support_case',
  label: 'Support Case',
  sharingModel: 'private',
  fields: {
    status: Field.select({
      label: 'Status',
      required: true,
      options: [
        { label: 'New', value: 'new' },
        { label: 'Assigned', value: 'assigned' },
        { label: 'Escalated', value: 'escalated' },
        { label: 'Resolved', value: 'resolved' },
      ],
    }),
  },
  validations: [
    {
      type: 'state_machine',
      name: 'case_status_flow',
      field: 'status',
      events: ['update'],
      message: 'Invalid case status transition.',
      transitions: {
        new: ['assigned'],
        assigned: ['resolved', 'escalated'],
        escalated: ['resolved'],
        resolved: [],
      },
    },
  ],
});

The rule declares which transitions are legal. Use Flow nodes for side effects around those transitions when you need notifications, record updates, or external calls.


Approvals

Approvals are Flow nodes:

{
  id: 'manager_approval',
  type: 'approval',
  label: 'Manager Approval',
  config: {
    approvers: [{ type: 'field', value: 'owner_manager_id' }],
    behavior: 'unanimous',
    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 conceptCurrent equivalent
Workflow RuleFlow
Time triggerScheduled Flow
Field update actionupdate_record node
Email alertnotify node
HTTP callhttp node
Approval ProcessFlow 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.

On this page