{"skill":{"slug":"work-fllows","displayName":"work-fllows","summary":"Guide AI to create NocoBase workflows — triggers, conditions, data operations, SQL, scheduling","description":"---\nname: nocobase-workflow\ndescription: Guide AI to create NocoBase workflows — triggers, conditions, data operations, SQL, scheduling\ntriggers:\n  - 工作流\n  - 自动化\n  - workflow\n  - automation\n  - trigger\n  - 触发器\n  - 定时任务\n  - 状态联动\ntools:\n  - nb_create_workflow\n  - nb_add_node\n  - nb_enable_workflow\n  - nb_list_workflows\n  - nb_get_workflow\n  - nb_delete_workflow\n  - nb_delete_workflows_by_prefix\n---\n\n# NocoBase Workflow Building\n\nYou are guiding the user to create automated workflows in NocoBase.\n\n## Key Concepts\n\n### Trigger Types\n\n| Type | Description | Mode |\n|------|-------------|------|\n| `collection` | Data change trigger | 1=create, 2=update, 3=create+update, 4=delete |\n| `schedule` | Time-based trigger | 0=cron, 1=date field |\n| `action` | Manual button trigger | — |\n\n### Node Types\n\n| Type | Description |\n|------|-------------|\n| `condition` | If/else branch (basic engine or math.js) |\n| `update` | Update existing records |\n| `create` | Create new records |\n| `query` | Query records for use in downstream nodes |\n| `sql` | Execute raw SQL |\n| `request` | HTTP request (webhooks, external APIs) |\n| `loop` | Iterate over array data |\n| `end` | Terminate workflow (1=success, 0=failure) |\n\n### Node Linking Model\n\n- Nodes form a **linked list**: `upstreamId` → `downstreamId`\n- Branch nodes (condition, loop) use `branchIndex`:\n  - `1` = true branch (condition) or loop body (loop)\n  - `0` = false branch (condition)\n  - `null` = main line continuation\n\n### Variable System\n\n| Variable | Description |\n|----------|-------------|\n| `{{$context.data.field}}` | Field from the trigger record |\n| `{{$context.data.id}}` | ID of the trigger record |\n| `{{$jobsMapByNodeKey.<key>.field}}` | Result from a previous node |\n| `{{$scopes.<key>.item}}` | Current item in a loop |\n\n## Workflow Patterns\n\n### Pattern 1: Auto-numbering (on create)\n\nGenerate sequential IDs like `PR-2026-001`:\n\n```\n# Step 1: Create workflow\nnb_create_workflow(\"Auto Purchase Number\", \"collection\",\n    '{\"mode\": 1, \"collection\": \"purchase_requests\", \"appends\": [], \"condition\": {\"$and\": []}}')\n\n# Step 2: Add SQL node\nnb_add_node(wf_id, \"sql\", \"Generate Number\",\n    '{\"dataSource\": \"main\", \"sql\": \"UPDATE purchase_requests SET request_no = \\'PR-\\' || TO_CHAR(NOW(), \\'YYYY\\') || \\'-\\' || LPAD((SELECT COALESCE(MAX(CAST(SUBSTRING(request_no FROM \\'[0-9]+$\\') AS INT)),0)+1 FROM purchase_requests WHERE request_no LIKE \\'PR-\\' || TO_CHAR(NOW(), \\'YYYY\\') || \\'-%\\')::TEXT, 3, \\'0\\') WHERE id = {{$context.data.id}}\"}')\n\n# Step 3: Enable\nnb_enable_workflow(wf_id)\n```\n\n### Pattern 2: Status Sync (on create)\n\nSet default status when a record is created:\n\n```\nnb_create_workflow(\"Default Status\", \"collection\",\n    '{\"mode\": 1, \"collection\": \"orders\", \"appends\": [], \"condition\": {\"$and\": []}}')\n\nnb_add_node(wf_id, \"update\", \"Set Draft Status\",\n    '{\"collection\": \"orders\", \"params\": {\"filter\": {\"id\": \"{{$context.data.id}}\"}, \"values\": {\"status\": \"draft\"}}}')\n\nnb_enable_workflow(wf_id)\n```\n\n### Pattern 3: Conditional Branch (on create)\n\nDifferent actions based on field value:\n\n```\nnb_create_workflow(\"Transfer Type Handler\", \"collection\",\n    '{\"mode\": 1, \"collection\": \"transfers\", \"appends\": [], \"condition\": {\"$and\": []}}')\n\n# Condition node\ncond = nb_add_node(wf_id, \"condition\", \"Is Transfer?\",\n    '{\"rejectOnFalse\": false, \"engine\": \"basic\", \"calculation\": {\"group\": {\"type\": \"and\", \"calculations\": [{\"calculator\": \"equal\", \"operands\": [\"{{$context.data.type}}\", \"transfer\"]}]}}}')\n\n# True branch: update status to \"transferred\"\nnb_add_node(wf_id, \"update\", \"Mark Transferred\",\n    '{\"collection\": \"assets\", \"params\": {\"filter\": {\"id\": \"{{$context.data.asset_id}}\"}, \"values\": {\"status\": \"transferred\"}}}',\n    upstream_id=cond_id, branch_index=1)\n\n# False branch: update status to \"borrowed\"\nnb_add_node(wf_id, \"update\", \"Mark Borrowed\",\n    '{\"collection\": \"assets\", \"params\": {\"filter\": {\"id\": \"{{$context.data.asset_id}}\"}, \"values\": {\"status\": \"borrowed\"}}}',\n    upstream_id=cond_id, branch_index=0)\n\nnb_enable_workflow(wf_id)\n```\n\n### Pattern 4: Field Change Trigger (on update)\n\nReact when specific fields change:\n\n```\nnb_create_workflow(\"Disposal Complete\", \"collection\",\n    '{\"mode\": 2, \"collection\": \"disposals\", \"changed\": [\"status\"], \"condition\": {\"status\": {\"$eq\": \"disposed\"}}, \"appends\": []}')\n\nnb_add_node(wf_id, \"update\", \"Update Asset Status\",\n    '{\"collection\": \"assets\", \"params\": {\"filter\": {\"id\": \"{{$context.data.asset_id}}\"}, \"values\": {\"status\": \"disposed\"}}}')\n\nnb_enable_workflow(wf_id)\n```\n\n### Pattern 5: Date-based Schedule\n\nTrigger N days before a date field:\n\n```\n# Trigger 30 days before insurance expiry\nnb_create_workflow(\"Insurance Expiry Warning\", \"schedule\",\n    '{\"mode\": 1, \"collection\": \"insurance\", \"startsOn\": {\"field\": \"end_date\", \"offset\": -30, \"unit\": 86400000}, \"repeat\": null, \"appends\": []}')\n\nnb_add_node(wf_id, \"sql\", \"Mark Expiring\",\n    '{\"dataSource\": \"main\", \"sql\": \"UPDATE insurance SET remark = \\'expiring soon\\' WHERE id = {{$context.data.id}}\"}')\n\nnb_enable_workflow(wf_id)\n```\n\n### Pattern 6: Cron Schedule\n\n```\n# Run every weekday at 9 AM\nnb_create_workflow(\"Daily Report\", \"schedule\",\n    '{\"mode\": 0, \"startsOn\": \"2026-01-01T00:00:00.000Z\", \"repeat\": \"0 9 * * 1-5\"}')\n```\n\n### Pattern 7: Multi-node Chain\n\nChain multiple nodes on the main line:\n\n```\nnb_create_workflow(\"Complex Flow\", \"collection\",\n    '{\"mode\": 1, \"collection\": \"orders\", \"appends\": [], \"condition\": {\"$and\": []}}')\n\n# Node 1 (first node, no upstream_id needed)\nn1 = nb_add_node(wf_id, \"query\", \"Get Customer\",\n    '{\"collection\": \"customers\", \"multiple\": false, \"params\": {\"filter\": {\"id\": \"{{$context.data.customer_id}}\"}}}')\n\n# Node 2 (chains after node 1)\nn2 = nb_add_node(wf_id, \"update\", \"Update Order\",\n    '{\"collection\": \"orders\", \"params\": {\"filter\": {\"id\": \"{{$context.data.id}}\"}, \"values\": {\"customer_name\": \"{{$jobsMapByNodeKey.NODE_KEY.name}}\"}}}',\n    upstream_id=n1_id)\n\nnb_enable_workflow(wf_id)\n```\n\n## Workflow\n\n### Phase 1: Plan\n\nBefore creating workflows, identify:\n1. **Trigger**: What event starts the workflow? (record created/updated/deleted, schedule, manual)\n2. **Collection**: Which table triggers it?\n3. **Logic**: What conditions and actions are needed?\n4. **Target**: What data gets modified?\n\n### Phase 2: Create\n\n1. Create workflow with trigger config\n2. Add nodes in order (first node auto-links, subsequent need `upstream_id`)\n3. For branches: add condition node, then true/false branch nodes with `branch_index`\n\n### Phase 3: Enable & Verify\n\n1. Enable the workflow\n2. List workflows to confirm: `nb_list_workflows()`\n3. Inspect with: `nb_get_workflow(wf_id)`\n\n## Best Practices\n\n1. **Title convention**: Use prefixes for grouping, e.g. \"AM-WF01 Auto Number\"\n2. **One workflow per concern**: Don't combine unrelated logic\n3. **Idempotent SQL**: Write SQL that's safe to run multiple times\n4. **Test before enable**: Create all nodes first, then enable\n5. **Batch cleanup**: Use `nb_delete_workflows_by_prefix(\"AM-\")` to clean up\n6. **Duplicate prevention**: Check `nb_list_workflows()` before creating — duplicate titles cause confusion\n7. **Variable references**: Always use double braces `{{$context.data.field}}` — single braces won't work\n","tags":{"latest":"1.0.0"},"stats":{"comments":0,"downloads":613,"installsAllTime":0,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1773714503727,"updatedAt":1778491961785},"latestVersion":{"version":"1.0.0","createdAt":1773714503727,"changelog":"技能工作流","license":"MIT-0"},"metadata":null,"owner":{"handle":"alexander-lq","userId":"s17bsr5h5y8tte9g03yknsxsdd83g0rv","displayName":"liu xiansen","image":"https://avatars.githubusercontent.com/u/49258980?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1780089922794}}