[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F47D2M79EZAHS5GVM8SSKN":3,"skill-same:01M6F47D2M79EZAHS5GVM8SSKN":80},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":52,"linkOnly":53,"markdownContent":54,"markdownBody":55,"frontMatter":56,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":62,"ownerHtmlUrl":63,"ownerBlogUrl":6,"ownerDescription":64,"updatedAt":65,"install":66,"resources":79},"01M6F47D2M79EZAHS5GVM8SSKN","n8n-workflow-sdk",null,"Builds, tests, validates, and manages n8n workflows programmatically using the @n8n\u002Fworkflow-sdk. Activates when the user wants to create n8n workflows with code, convert between JSON and code, validate workflows, generate test\u002Fpin data, build AI agent workflows, or work with the n8n workflow SDK in any capacity. Also triggers on mentions of workflow-sdk, workflow builder, n8n workflow code, workflow JSON, workflow validation, code generation, or programmatic workflow creation.","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",67,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fgeckse\u002Fn8n-skills\u002Fn8n-workflow-sdk\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"n8n workflow sdk","workflow","n8n workflow","n8n","sdk","pin","triggers","tests","creation","builder","workflow builder","work","convert","data","Code Generation","code","generate","create","workflows","ai agent","2026-11-10 10:12:30",false,"---\nname: n8n-workflow-sdk\ndescription: \"Builds, tests, validates, and manages n8n workflows programmatically using the @n8n\u002Fworkflow-sdk. Activates when the user wants to create n8n workflows with code, convert between JSON and code, validate workflows, generate test\u002Fpin data, build AI agent workflows, or work with the n8n workflow SDK in any capacity. Also triggers on mentions of workflow-sdk, workflow builder, n8n workflow code, workflow JSON, workflow validation, code generation, or programmatic workflow creation.\"\n---\n\n# n8n Workflow SDK\n\nBuild, test, validate, and manage n8n workflows programmatically using the `@n8n\u002Fworkflow-sdk`. This skill enables code-based workflow construction with full validation, JSON round-tripping, and testing support.\n\n## When You Need More Detail\n\nThis skill uses progressive disclosure. The SKILL.md covers the full workflow, decision-making, and quick-reference API. For complete patterns and examples, read these reference files:\n\n- `references\u002Fworkflow-building.md` — Workflow creation, settings, JSON import\u002Fexport, and complete building patterns\n- `references\u002Fnodes-and-connections.md` — All node factories, subnodes (AI\u002FLangChain), connection patterns, and credentials\n- `references\u002Fcontrol-flow.md` — IF\u002FElse, Switch\u002FCase, Split in Batches, Merge, error handling, and branching\n- `references\u002Fexpressions-and-code.md` — Expression system, Code node helpers, fromAi, and expression context\n- `references\u002Fvalidation-and-testing.md` — Workflow validation, pin data, test data generation, and testing strategies\n- `references\u002Fcode-generation.md` — JSON-to-code, code-to-JSON, parseWorkflowCode, and the generation pipeline\n- `references\u002Fplugins-and-advanced.md` — Plugin system, type generation, Zod schemas, and extensibility\n- `references\u002Fnode-registry-official.json` — Cached registry of all 556 official n8n nodes (name, version, description)\n- `references\u002Fnode-registry-community.json` — Cached registry of community nodes (name, version, packageName)\n- `references\u002Fnode-registry-properties.jsonl` — Node properties\u002Fparameters (one JSON line per node, grep for a specific node)\n\nRead the appropriate reference file before writing any code.\n\n## Project Setup\n\nBefore writing SDK code, the project needs `@n8n\u002Fworkflow-sdk` and TypeScript tooling installed. If the project already has these, skip this section.\n\n**For a new project:**\n\n```bash\nmkdir my-workflow-project && cd my-workflow-project\nnpm init -y\nnpm install @n8n\u002Fworkflow-sdk\nnpm install --save-dev typescript tsx @types\u002Fnode\n```\n\n**For an existing project:**\n\n```bash\nnpm install @n8n\u002Fworkflow-sdk\nnpm install --save-dev typescript tsx @types\u002Fnode\n```\n\n**Running workflow scripts:**\n\n```bash\nnpx tsx src\u002Fmy-workflow.ts\n```\n\nThe SDK is a pure TypeScript\u002FJavaScript library — no build step is required. `tsx` runs `.ts` files directly. If the project already uses `ts-node` or another TypeScript runner, that works too.\n\n## CRITICAL: Node Type Lookup — Never Guess Node Types\n\n**DO NOT invent or guess node type identifiers.** n8n has hundreds of nodes, each with a specific `type` string (e.g., `n8n-nodes-base.slack`). Using a wrong or made-up type produces a workflow that fails in n8n.\n\n**Always look up the real node type** from the cached node registry before using it.\n\n### Local Registry Cache (Primary Source)\n\nThis skill ships with cached copies of the n8n node registries. **Read these files to look up node types** — no network requests needed:\n\n- **`references\u002Fnode-registry-official.json`** — Index of all 556 built-in n8n nodes (name, version, description)\n- **`references\u002Fnode-registry-community.json`** — Index of community-contributed nodes (name, version, packageName)\n- **`references\u002Fnode-registry-properties.jsonl`** — Full node properties\u002Fparameters (one JSON line per node)\n\n#### Index files (for finding the right node)\n\nEach official node entry has:\n- `name` — The **node type identifier** to use in `node()` \u002F `trigger()` (e.g., `\"n8n-nodes-base.slack\"`)\n- `displayName` — Human-readable name (e.g., `\"Slack\"`)\n- `version` — Current version number\n- `description` — What the node does\n- `group` — `\"[\\\"trigger\\\"]\"` or other classification\n- `alias` — Alternative names for search (e.g., `[\"message\", \"chat\"]`)\n- `categories` — Node categories (e.g., `[\"Communication\"]`)\n\nEach community node entry has:\n- `name` — The **node type identifier**\n- `displayName` — Human-readable name\n- `packageName` — The **npm package** the user must install (e.g., `\"@mendable\u002Fn8n-nodes-firecrawl\"`)\n- `version` — Version number\n- `isOfficialNode` — Whether verified by n8n\n\n**How to find a node:** Read the appropriate registry JSON index and search for the node by `displayName`, `name`, `alias`, or `description`.\n\n#### Properties file (for configuring a node)\n\nOnce you've found a node's `name` in the index, **grep the properties file** to get its available parameters:\n\n```\ngrep 'n8n-nodes-base.slack' references\u002Fnode-registry-properties.jsonl\n```\n\nEach line is a JSON object: `{\"node\": \"n8n-nodes-base.slack\", \"properties\": [...]}` where `properties` contains the node's parameter definitions — field names, types, defaults, options, required flags, and conditional display rules. Use this to correctly configure the `parameters` object in `node()` \u002F `trigger()`.\n\n**CRITICAL: Use option `value`, not display `name`.** For `options`-type parameters, the properties JSONL lists both a display `name` (what the UI shows) and the actual `value` (what goes in the workflow JSON). These are often different — you **must** use the `value`. Common traps:\n\n| Node | Parameter | Display Name | Actual Value |\n|------|-----------|-------------|--------------|\n| Set | `mode` | `JSON` | `raw` |\n| HTTP Request | `contentType` | `Form Urlencoded` | `form-urlencoded` |\n| Code | `language` | `Python` | `pythonNative` |\n| Webhook | `responseMode` | `Immediately` | `onReceived` |\n| Merge | `mode` | `SQL Query` | `combineBySql` |\n\n**CRITICAL: Set node v3.3+\u002F3.4 uses `assignments`, NOT `fields`.** The parameter schema changed at v3.3. If you're using Set node v3.4, you MUST use `assignments.assignments` (with `type: 'string'`\u002F`'number'`\u002F`'boolean'` and a single `value` field). The old `fields.values` \u002F `stringValue` \u002F `numberValue` format is for v3.0–3.2 only. See `nodes-and-connections.md` for full examples.\n\n#### Deriving version hints from properties data\n\nWhen the registry cache `version` field appears to be a rounded major version (e.g., `4` instead of `4.4`), you can derive the actual latest version from `@version` hints in the properties JSONL. About 96 of 554 nodes include these hints.\n\nNode properties use `displayOptions.show[\"@version\"]` and `displayOptions.hide[\"@version\"]` to control which fields appear for which versions. These arrays contain the version numbers the node supports, letting you infer the latest version.\n\n**How to extract version hints:**\n\n1. Grep the properties file for the node: `grep 'n8n-nodes-base.set' references\u002Fnode-registry-properties.jsonl`\n2. Search the JSON for all `\"@version\"` values in `displayOptions`\n3. Collect all version numbers — they appear in two forms:\n   - **Explicit lists:** `\"@version\": [3, 3.1, 3.2]` — the node supports versions 3, 3.1, and 3.2\n   - **Conditions:** `\"@version\": [{\"_cnd\": {\"gte\": 3.1}}]` — applies to version ≥ 3.1\n4. The **maximum** value across all explicit numbers and condition thresholds approximates the latest minor version\n\n**Examples from real nodes:**\n- **Set node** — `\"@version\": [3, 3.1, 3.2]` → latest is at least `3.2` (actual defaultVersion: `3.4`)\n- **Agent node** — `\"@version\": [{\"_cnd\": {\"gte\": 3.1}}]` → latest is at least `3.1` (actual: `3.1`)\n- **Switch node** — `\"@version\": [{\"_cnd\": {\"gte\": 3.3}}]` → latest is at least `3.3` (actual: `3.4`)\n\n**Limitations:** This technique only provides a lower bound (the actual `defaultVersion` may be higher). ~458 of 554 nodes have no `@version` hints at all. The registry cache `version` field (updated via `refresh-node-registry.sh` which extracts real `defaultVersion` from npm packages) is always the most accurate source.\n\n### Refreshing the Cache\n\nIf a node can't be found in the cached registries (they may be outdated), **run the refresh script** to pull the latest data from the live APIs:\n\n```bash\nbash scripts\u002Frefresh-node-registry.sh\n```\n\nThis fetches from `https:\u002F\u002Fapi.n8n.io\u002Fapi\u002Fnodes` and `https:\u002F\u002Fapi.n8n.io\u002Fapi\u002Fcommunity-nodes`, installs the `n8n-nodes-base` and `@n8n\u002Fn8n-nodes-langchain` npm packages to extract accurate `defaultVersion` values (the API only returns major versions), strips heavy fields, and updates all three cache files. Run this when you suspect the cache is stale or when a user asks for a node that should exist but isn't in the cache.\n\n### Community Node Warning\n\n**When using a community node**, the user must install the npm package in their n8n instance first. **Always add a `sticky()` note** to the workflow warning about the required community node package:\n\n```typescript\nconst installNote = sticky(\n  '⚠️ Required Community Node: Install the \"@mendable\u002Fn8n-nodes-firecrawl\" package in your n8n instance (Settings → Community Nodes → Install) before using this workflow.',\n  [firecrawlNode],\n  { color: 5 }\n)\n```\n\n### Lookup Workflow\n\n1. **User asks for a specific integration** (e.g., \"add a Slack node\")\n2. **Read `references\u002Fnode-registry-official.json`** and search for the matching node\n3. **If not found in official nodes**, read `references\u002Fnode-registry-community.json` and search there\n4. **If still not found**, run `bash scripts\u002Frefresh-node-registry.sh` to update the cache, then search again\n5. **Use the exact `name` and `version`** from the registry in your `node()` \u002F `trigger()` call\n6. **Grep `references\u002Fnode-registry-properties.jsonl`** for the node name to get its available parameters and configure it correctly\n7. **If using a community node**, add a `sticky()` note listing the required npm package\n8. **If no matching node exists anywhere**, tell the user — do not invent a type\n\n### Quick Reference: Core Utility Nodes\n\nThese fundamental utility node **type names** are always safe to use without a registry lookup. However, **always look up the correct `version`** from `references\u002Fnode-registry-official.json` — do NOT copy version numbers from code examples, as they may be outdated.\n\n| Type | Description |\n|------|-------------|\n| `n8n-nodes-base.manualTrigger` | Manual execution trigger |\n| `n8n-nodes-base.webhook` | HTTP webhook trigger |\n| `n8n-nodes-base.scheduleTrigger` | Cron\u002Finterval trigger |\n| `n8n-nodes-base.httpRequest` | Generic HTTP request |\n| `n8n-nodes-base.set` | Set\u002Ftransform data fields (v3.4 uses `assignments` format — never `fields.values` or `{ options: {} }`) |\n| `n8n-nodes-base.code` | Custom JavaScript\u002FPython code |\n| `n8n-nodes-base.if` | Conditional branching |\n| `n8n-nodes-base.switch` | Multi-branch routing |\n| `n8n-nodes-base.merge` | Merge multiple inputs |\n| `n8n-nodes-base.splitInBatches` | Batch processing loop |\n| `n8n-nodes-base.noOp` | No operation (passthrough) |\n| `n8n-nodes-base.stickyNote` | Canvas annotation |\n| `n8n-nodes-base.respondToWebhook` | Respond to webhook |\n| `n8n-nodes-base.filter` | Filter items |\n| `n8n-nodes-base.executeWorkflow` | Execute sub-workflow |\n| `@n8n\u002Fn8n-nodes-langchain.agent` | AI Agent |\n\n**⚠️ IMPORTANT: Version numbers in code examples throughout this skill are illustrative and may be outdated.** The **only reliable source** for the current version of any node is `references\u002Fnode-registry-official.json` (or `references\u002Fnode-registry-community.json` for community nodes). Always read the registry cache to get the correct `version` before using any node, even core utility nodes listed above.\n\n**For ANY integration node not in this list** (Slack, Gmail, Notion, Airtable, Google Sheets, Postgres, etc.), you MUST look up the correct `type` and `version` from the registry cache before using it.\n\n## Core Concepts\n\nThe SDK provides a **fluent, chainable API** for building n8n workflows in TypeScript\u002FJavaScript. Instead of hand-crafting JSON, you construct workflows programmatically with full type safety, validation, and testing capabilities.\n\n**Key capabilities:**\n1. **Create workflows** — Fluent builder API with `workflow()`, `node()`, `trigger()`\n2. **Import existing workflows** — `workflow.fromJSON(json)` loads any n8n workflow JSON\n3. **Export to JSON** — `.toJSON()` produces n8n-compatible workflow JSON\n4. **Convert JSON to code** — `generateWorkflowCode(json)` produces readable SDK code\n5. **Convert code to JSON** — `parseWorkflowCode(code)` parses SDK code back to JSON\n6. **Validate** — `validateWorkflow()` checks structure, connections, expressions, and best practices\n7. **Test with pin data** — `.generatePinData()` creates mock data from output declarations\n8. **Build AI workflows** — Full support for LangChain subnodes (models, tools, memory, etc.)\n\n## Common Workflows\n\n### Pattern 1: Create a New Workflow from Scratch\n\n```typescript\nimport { workflow, node, trigger, validateWorkflow } from '@n8n\u002Fworkflow-sdk'\n\nconst myTrigger = trigger({ type: 'n8n-nodes-base.manualTrigger', version: 1, config: {} })\nconst httpNode = node({\n  type: 'n8n-nodes-base.httpRequest', version: 4.4,\n  config: {\n    parameters: { url: 'https:\u002F\u002Fapi.example.com\u002Fdata', method: 'GET' },\n    output: [{ json: { id: 1, name: 'Example' } }]\n  }\n})\n\nconst wf = workflow('my-workflow', 'My Workflow')\n  .add(myTrigger)\n  .to(httpNode)\n  .generatePinData()\n\nconst validation = validateWorkflow(wf)\nif (validation.valid) {\n  const json = wf.toJSON()  \u002F\u002F Ready for n8n\n}\n```\n\n### Pattern 2: Load Existing JSON, Modify, Test, Export\n\n```typescript\nimport { workflow, node, generateWorkflowCode, validateWorkflow } from '@n8n\u002Fworkflow-sdk'\n\n\u002F\u002F 1. Load from JSON (user provides their workflow JSON)\nconst wf = workflow.fromJSON(existingWorkflowJSON)\n\n\u002F\u002F 2. Convert to code to understand it\nconst code = generateWorkflowCode(existingWorkflowJSON)\n\n\u002F\u002F 3. Modify — add a new node\nconst newNode = node({\n  type: 'n8n-nodes-base.set', version: 3.4,\n  config: {\n    name: 'Transform',\n    parameters: {\n      mode: 'manual',\n      assignments: {\n        assignments: [\n          { name: 'processed', value: true, type: 'boolean' },\n          { name: 'label', value: '={{ $json.name }}', type: 'string' }\n        ]\n      },\n      includeOtherFields: true,\n      options: {}\n    }\n  }\n})\n\u002F\u002F Rebuild with modifications using the SDK\n\n\u002F\u002F 4. Validate\nconst result = validateWorkflow(wf)\nconsole.log(result.errors, result.warnings)\n\n\u002F\u002F 5. Export back to JSON\nconst updatedJSON = wf.toJSON()\n```\n\n### Pattern 3: Convert JSON to Code, Modify, Convert Back\n\n```typescript\nimport { generateWorkflowCode, parseWorkflowCode, validateWorkflow } from '@n8n\u002Fworkflow-sdk'\n\n\u002F\u002F JSON → Code (for reading\u002Funderstanding)\nconst code = generateWorkflowCode(workflowJSON)\n\n\u002F\u002F Modify the code as needed...\n\n\u002F\u002F Code → JSON (for importing back to n8n)\nconst newJSON = parseWorkflowCode(modifiedCode)\n\n\u002F\u002F Validate before deploying\nconst result = validateWorkflow(newJSON)\n```\n\n### Pattern 4: Build an AI Agent Workflow\n\n```typescript\nimport { workflow, node, trigger, languageModel, tool, memory, fromAi } from '@n8n\u002Fworkflow-sdk'\n\nconst model = languageModel({\n  type: '@n8n\u002Fn8n-nodes-langchain.lmChatOpenAi', version: 1.3,\n  config: {\n    parameters: {\n      model: { __rl: true, mode: 'list', value: 'gpt-4o-mini', cachedResultName: 'gpt-4o-mini' },\n      options: {}\n    },\n    credentials: { openAiApi: { name: 'OpenAI', id: 'cred-123' } }\n  }\n})\n\nconst emailTool = tool({\n  type: '@n8n\u002Fn8n-nodes-langchain.toolGmail', version: 1,\n  config: {\n    parameters: {\n      recipient: fromAi('recipient', 'Email recipient address'),\n      subject: fromAi('subject', 'Email subject line')\n    }\n  }\n})\n\nconst agent = node({\n  type: '@n8n\u002Fn8n-nodes-langchain.agent', version: 3.1,\n  config: {\n    name: 'AI Agent',\n    parameters: {\n      promptType: 'define',\n      text: '={{ $json.chatInput }}',\n      options: { systemMessage: 'You are a helpful AI assistant.' }\n    },\n    subnodes: { model, tools: [emailTool], memory: memoryNode }\n  }\n})\n\nconst wf = workflow('ai-workflow', 'AI Agent Workflow')\n  .add(trigger({ type: 'n8n-nodes-base.manualTrigger', version: 1, config: {} }))\n  .to(agent)\n```\n\n**⚠️ Model IDs in examples are illustrative and will become outdated.** Always choose the most appropriate **current** model for the user's use case — do NOT copy model IDs like `gpt-4o-mini` from these examples. Check the provider's latest model offerings.\n\n## SDK API Quick Reference\n\n### Factory Functions\n\n| Function | Purpose | Reference |\n|----------|---------|-----------|\n| `workflow(id, name, options?)` | Create workflow builder | `references\u002Fworkflow-building.md` |\n| `workflow.fromJSON(json)` | Import from n8n JSON | `references\u002Fworkflow-building.md` |\n| `node(input)` | Create a regular node | `references\u002Fnodes-and-connections.md` |\n| `trigger(input)` | Create a trigger node | `references\u002Fnodes-and-connections.md` |\n| `sticky(content, nodes?, config?)` | Create a sticky note | `references\u002Fnodes-and-connections.md` |\n| `placeholder(hint)` | Create a placeholder value | `references\u002Fnodes-and-connections.md` |\n| `newCredential(name)` | Mark credential as new | `references\u002Fnodes-and-connections.md` |\n| `ifElse(config)` | Create IF node | `references\u002Fcontrol-flow.md` |\n| `switchCase(config)` | Create Switch node | `references\u002Fcontrol-flow.md` |\n| `merge(config)` | Create Merge node | `references\u002Fcontrol-flow.md` |\n| `splitInBatches(config)` | Create Split In Batches | `references\u002Fcontrol-flow.md` |\n| `nextBatch(sib)` | Loop-back for SIB | `references\u002Fcontrol-flow.md` |\n\n### Subnode Factories (AI\u002FLangChain)\n\n| Function | Purpose |\n|----------|---------|\n| `languageModel(input)` | Language model (OpenAI, Anthropic, etc.) |\n| `memory(input)` | Memory (Buffer Window, etc.) |\n| `tool(input)` | Tool (Calculator, Code, Gmail, etc.) |\n| `outputParser(input)` | Output parser |\n| `embedding(input)` \u002F `embeddings(input)` | Embedding model |\n| `vectorStore(input)` | Vector store (Pinecone, Qdrant, etc.) |\n| `retriever(input)` | Retriever |\n| `documentLoader(input)` | Document loader |\n| `textSplitter(input)` | Text splitter |\n| `fromAi(key, description?, type?, defaultValue?)` | AI-driven parameter |\n\n### Expression Utilities\n\n| Function | Purpose |\n|----------|---------|\n| `serializeExpression(fn)` | `$ => $.json.name` → `={{ $json.name }}` |\n| `parseExpression(expr)` | `={{ $json.name }}` → `$json.name` |\n| `isExpression(value)` | Check if string is an expression |\n| `expr(expression)` | Mark string as expression (adds `=` prefix) |\n\n### Code Helpers\n\n| Function | Purpose |\n|----------|---------|\n| `runOnceForAllItems(fn)` | Code node: process all items at once |\n| `runOnceForEachItem(fn)` | Code node: process one item at a time |\n\n### Validation & Testing\n\n| Function | Purpose |\n|----------|---------|\n| `validateWorkflow(wf, options?)` | Validate workflow structure |\n| `.generatePinData(options?)` | Generate pin\u002Ftest data from outputs |\n| `.validate(options?)` | Validate on the builder directly |\n\n### Code Generation\n\n| Function | Purpose |\n|----------|---------|\n| `generateWorkflowCode(json)` | JSON → TypeScript SDK code |\n| `parseWorkflowCode(code)` | SDK code → JSON |\n| `parseWorkflowCodeToBuilder(code)` | SDK code → WorkflowBuilder |\n\n### WorkflowBuilder Methods\n\n| Method | Purpose |\n|--------|---------|\n| `.add(node)` | Add node to workflow |\n| `.to(target)` | Connect current node to target |\n| `.connect(src, srcOut, tgt, tgtIn)` | Explicit indexed connection |\n| `.settings(settings)` | Set workflow settings |\n| `.getNode(name)` | Get node by name |\n| `.validate(options?)` | Validate the workflow |\n| `.toJSON()` | Export as n8n JSON |\n| `.toFormat(format)` | Serialize with plugin |\n| `.generatePinData(options?)` | Generate test data |\n| `.regenerateNodeIds()` | Regenerate deterministic IDs |\n\n### NodeInstance Methods\n\n| Method | Purpose |\n|--------|---------|\n| `.to(target, outputIndex?)` | Connect to next node(s) |\n| `.input(index)` | Create input target (for Merge) |\n| `.output(index)` | Select specific output index |\n| `.onTrue(target)` | IF node: true branch |\n| `.onFalse(target)` | IF node: false branch |\n| `.onCase(index, target)` | Switch node: case branch |\n| `.onError(handler)` | Set error handler |\n| `.update(config)` | Create updated copy |\n| `.getConnections()` | Get declared connections |\n\n## Decision Guide\n\n### When to Use Each Pattern\n\n**Creating a brand-new workflow:**\n1. **Look up node types** — Read `references\u002Fnode-registry-official.json` (and `references\u002Fnode-registry-community.json` if needed) to get the correct `type` and `version` for every integration node\n2. Design the flow (trigger → processing → output)\n3. Create nodes with `node()` and `trigger()` using the real types from the registry\n4. If using community nodes, add `sticky()` notes listing required npm packages\n5. Chain with `.add()` and `.to()`\n6. Add output declarations for testing\n7. Validate with `validateWorkflow()`\n8. Export with `.toJSON()`\n\n**Modifying an existing n8n workflow (JSON):**\n1. Load with `workflow.fromJSON(json)`\n2. Optionally convert to code with `generateWorkflowCode(json)` to understand it\n3. Build the modified version using the SDK\n4. Validate the new version\n5. Export with `.toJSON()`\n\n**Understanding a complex workflow:**\n1. Convert with `generateWorkflowCode(json)` — produces readable TypeScript\n2. Read the generated code to understand the flow\n\n**Testing\u002Fverifying a workflow:**\n1. Build or load the workflow\n2. Add `output` declarations to nodes (expected output shapes)\n3. Call `.generatePinData()` to create test data\n4. Call `validateWorkflow()` to check for errors\n5. Review validation errors and warnings\n\n**Round-tripping (code ↔ JSON):**\n- JSON → Code: `generateWorkflowCode(json)`\n- Code → JSON: `parseWorkflowCode(code)`\n- Code → Builder: `parseWorkflowCodeToBuilder(code)` (allows validation before JSON export)\n\n## Connection Patterns Summary\n\n```typescript\n\u002F\u002F Sequential: A → B → C\nworkflow('id', 'name').add(a).to(b).to(c)\n\n\u002F\u002F Fan-out: A → [B, C, D] (parallel branches, output indices 0, 1, 2)\nworkflow('id', 'name').add(a).to([b, c, d])\n\n\u002F\u002F Multi-input (Merge): A → merge.input(0), B → merge.input(1)\nworkflow('id', 'name')\n  .add(a).to(merge.input(0))\n  .add(b).to(merge.input(1))\n\n\u002F\u002F Explicit indexed: source output 0 → target input 1\nworkflow('id', 'name').connect(source, 0, target, 1)\n\n\u002F\u002F Output selection: use specific output of a multi-output node\nnodeA.output(0).to(nodeB)\nnodeA.output(1).to(nodeC)\n\n\u002F\u002F IF branching\nifNode.onTrue(trueNode).onFalse(falseNode)\n\n\u002F\u002F Switch branching\nswitchNode.onCase(0, caseA).onCase(1, caseB).onCase(2, caseC)\n\n\u002F\u002F Error handling\nsourceNode.onError(errorHandler)\n\n\u002F\u002F Split in Batches loop\nsib.onEachBatch(process.to(nextBatch(sib))).onDone(finalNode)\n```\n\n## Validation Error Codes\n\nThe SDK validates for 23+ error conditions. Key ones to watch for:\n\n| Code | Meaning |\n|------|---------|\n| `NO_NODES` | Workflow has no nodes |\n| `MISSING_TRIGGER` | No trigger node found |\n| `DISCONNECTED_NODE` | Node not connected to flow |\n| `INVALID_CONNECTION` | Invalid connection between nodes |\n| `MISSING_PARAMETER` | Required parameter missing |\n| `CIRCULAR_REFERENCE` | Circular connection detected |\n| `INVALID_EXPRESSION` | Malformed expression |\n| `AGENT_STATIC_PROMPT` | AI agent has static prompt |\n| `HARDCODED_CREDENTIALS` | Credentials hardcoded in parameters |\n| `MERGE_SINGLE_INPUT` | Merge node has only one input |\n| `TOOL_NO_PARAMETERS` | Tool has no AI-driven parameters |\n| `FROM_AI_IN_NON_TOOL` | `fromAi()` used outside a tool node |\n| `SUBNODE_NOT_CONNECTED` | Subnode not connected to parent |\n\nSee `references\u002Fvalidation-and-testing.md` for the complete list and resolution strategies.\n\n## Node Type Constants\n\n```typescript\nimport { NODE_TYPES } from '@n8n\u002Fworkflow-sdk'\n\nNODE_TYPES.IF                  \u002F\u002F 'n8n-nodes-base.if'\nNODE_TYPES.SWITCH              \u002F\u002F 'n8n-nodes-base.switch'\nNODE_TYPES.MERGE               \u002F\u002F 'n8n-nodes-base.merge'\nNODE_TYPES.STICKY_NOTE         \u002F\u002F 'n8n-nodes-base.stickyNote'\nNODE_TYPES.SPLIT_IN_BATCHES    \u002F\u002F 'n8n-nodes-base.splitInBatches'\nNODE_TYPES.HTTP_REQUEST        \u002F\u002F 'n8n-nodes-base.httpRequest'\nNODE_TYPES.WEBHOOK             \u002F\u002F 'n8n-nodes-base.webhook'\nNODE_TYPES.DATA_TABLE          \u002F\u002F 'n8n-nodes-base.dataTable'\n```\n\n## Type Guards\n\n```typescript\nimport {\n  isIfNodeType, isSwitchNodeType, isMergeNodeType,\n  isStickyNoteType, isSplitInBatchesType, isHttpRequestType,\n  isWebhookType, isDataTableType, isNodeChain, isNodeInstance\n} from '@n8n\u002Fworkflow-sdk'\n```\n\n## Scope\n\nThis skill is for **building and manipulating n8n workflows programmatically** using the `@n8n\u002Fworkflow-sdk`. It covers workflow creation, JSON import\u002Fexport, validation, code generation, pin data, and the full SDK API.\n\n**This skill is NOT for:**\n- Deploying workflows to n8n instances or managing n8n server configuration\n- Building custom n8n community nodes (use the `n8n-node-builder` skill instead)\n- Using the n8n UI or REST API directly\n- Managing n8n credentials in a running instance\n\n## Best Practices\n\n1. **NEVER guess node types or versions — always look them up** — Read `references\u002Fnode-registry-official.json` (official) or `references\u002Fnode-registry-community.json` (community) to get the real `type` and `version`. Do NOT copy version numbers from code examples in this skill — they are illustrative and may be outdated. The registry cache is the single source of truth for versions. A wrong node type or version produces a broken workflow.\n2. **Flag community nodes with sticky notes** — If the workflow uses community nodes, add a `sticky()` note listing the npm packages the user must install in their n8n instance.\n3. **Always validate before exporting** — Call `validateWorkflow()` before `.toJSON()`. The SDK catches 23+ error conditions that would silently produce broken workflows in n8n, so skipping validation means shipping bugs.\n4. **Use output declarations for testing** — Add `output` to node configs, then call `generatePinData()`. Without output declarations, there's no way to generate test fixtures automatically, and downstream nodes can't be tested with realistic data shapes.\n5. **Use `generateWorkflowCode()` to understand existing workflows** — Raw n8n JSON is verbose and hard to follow. The generated TypeScript code reveals the logical flow, branching, and node relationships at a glance.\n6. **Use `parseWorkflowCode()` for round-tripping** — Code → JSON → Code round-trips cleanly, making it safe to convert, edit in code, and convert back without data loss.\n7. **Use `parseWorkflowCodeToBuilder()` when you need validation** — Unlike `parseWorkflowCode()` which returns raw JSON, this returns a WorkflowBuilder so you can `.validate()` before `.toJSON()`, catching errors before they reach n8n.\n8. **Use credentials objects, not hardcoded values** — Hardcoded API keys in parameters are a security risk and won't work across environments. The validator catches `HARDCODED_CREDENTIALS` to prevent this.\n9. **Use `fromAi()` only in tool nodes** — `fromAi()` generates `$fromAI` expressions that only the AI Agent node can resolve. Using them in regular nodes produces expressions that will fail at runtime. The validator catches `FROM_AI_IN_NON_TOOL`.\n10. **Use `expr()` for dynamic values** — Raw strings with `={{ }}` are error-prone (missing `=` prefix, unbalanced braces). `expr()` handles the prefix automatically and makes intent clear in code.\n11. **Set `output` on nodes to enable pin data generation** — This is how you create test fixtures. Without declared outputs, `generatePinData()` has nothing to convert.\n12. **Use `sticky()` for workflow documentation** — Auto-positions around given nodes, making it easy to annotate sections of complex workflows for other developers.\n13. **NEVER generate empty Set nodes** — A Set node with `parameters: { options: {} }` is broken and does nothing. Every Set node v3.4 MUST have `mode: 'manual'` with `assignments.assignments` containing at least one entry, OR `mode: 'raw'` with a `jsonOutput` string. Use `type: 'string'`\u002F`'number'`\u002F`'boolean'` and a single `value` field — NOT the old `stringValue`\u002F`numberValue` format. If you don't know what fields to set, ask the user.\n14. **Use current AI models, not example model IDs** — Model IDs in code examples (e.g., `gpt-4o-mini`, `claude-sonnet-4-20250514`) are illustrative and become outdated. Always select the most appropriate current model for the user's task. Check the provider's latest offerings rather than copying from examples.\n\n","# n8n Workflow SDK\n\nBuild, test, validate, and manage n8n workflows programmatically using the `@n8n\u002Fworkflow-sdk`. This skill enables code-based workflow construction with full validation, JSON round-tripping, and testing support.\n\n## When You Need More Detail\n\nThis skill uses progressive disclosure. The SKILL.md covers the full workflow, decision-making, and quick-reference API. For complete patterns and examples, read these reference files:\n\n- `references\u002Fworkflow-building.md` — Workflow creation, settings, JSON import\u002Fexport, and complete building patterns\n- `references\u002Fnodes-and-connections.md` — All node factories, subnodes (AI\u002FLangChain), connection patterns, and credentials\n- `references\u002Fcontrol-flow.md` — IF\u002FElse, Switch\u002FCase, Split in Batches, Merge, error handling, and branching\n- `references\u002Fexpressions-and-code.md` — Expression system, Code node helpers, fromAi, and expression context\n- `references\u002Fvalidation-and-testing.md` — Workflow validation, pin data, test data generation, and testing strategies\n- `references\u002Fcode-generation.md` — JSON-to-code, code-to-JSON, parseWorkflowCode, and the generation pipeline\n- `references\u002Fplugins-and-advanced.md` — Plugin system, type generation, Zod schemas, and extensibility\n- `references\u002Fnode-registry-official.json` — Cached registry of all 556 official n8n nodes (name, version, description)\n- `references\u002Fnode-registry-community.json` — Cached registry of community nodes (name, version, packageName)\n- `references\u002Fnode-registry-properties.jsonl` — Node properties\u002Fparameters (one JSON line per node, grep for a specific node)\n\nRead the appropriate reference file before writing any code.\n\n## Project Setup\n\nBefore writing SDK code, the project needs `@n8n\u002Fworkflow-sdk` and TypeScript tooling installed. If the project already has these, skip this section.\n\n**For a new project:**\n\n```bash\nmkdir my-workflow-project && cd my-workflow-project\nnpm init -y\nnpm install @n8n\u002Fworkflow-sdk\nnpm install --save-dev typescript tsx @types\u002Fnode\n```\n\n**For an existing project:**\n\n```bash\nnpm install @n8n\u002Fworkflow-sdk\nnpm install --save-dev typescript tsx @types\u002Fnode\n```\n\n**Running workflow scripts:**\n\n```bash\nnpx tsx src\u002Fmy-workflow.ts\n```\n\nThe SDK is a pure TypeScript\u002FJavaScript library — no build step is required. `tsx` runs `.ts` files directly. If the project already uses `ts-node` or another TypeScript runner, that works too.\n\n## CRITICAL: Node Type Lookup — Never Guess Node Types\n\n**DO NOT invent or guess node type identifiers.** n8n has hundreds of nodes, each with a specific `type` string (e.g., `n8n-nodes-base.slack`). Using a wrong or made-up type produces a workflow that fails in n8n.\n\n**Always look up the real node type** from the cached node registry before using it.\n\n### Local Registry Cache (Primary Source)\n\nThis skill ships with cached copies of the n8n node registries. **Read these files to look up node types** — no network requests needed:\n\n- **`references\u002Fnode-registry-official.json`** — Index of all 556 built-in n8n nodes (name, version, description)\n- **`references\u002Fnode-registry-community.json`** — Index of community-contributed nodes (name, version, packageName)\n- **`references\u002Fnode-registry-properties.jsonl`** — Full node properties\u002Fparameters (one JSON line per node)\n\n#### Index files (for finding the right node)\n\nEach official node entry has:\n- `name` — The **node type identifier** to use in `node()` \u002F `trigger()` (e.g., `\"n8n-nodes-base.slack\"`)\n- `displayName` — Human-readable name (e.g., `\"Slack\"`)\n- `version` — Current version number\n- `description` — What the node does\n- `group` — `\"[\\\"trigger\\\"]\"` or other classification\n- `alias` — Alternative names for search (e.g., `[\"message\", \"chat\"]`)\n- `categories` — Node categories (e.g., `[\"Communication\"]`)\n\nEach community node entry has:\n- `name` — The **node type identifier**\n- `displayName` — Human-readable name\n- `packageName` — The **npm package** the user must install (e.g., `\"@mendable\u002Fn8n-nodes-firecrawl\"`)\n- `version` — Version number\n- `isOfficialNode` — Whether verified by n8n\n\n**How to find a node:** Read the appropriate registry JSON index and search for the node by `displayName`, `name`, `alias`, or `description`.\n\n#### Properties file (for configuring a node)\n\nOnce you've found a node's `name` in the index, **grep the properties file** to get its available parameters:\n\n```\ngrep 'n8n-nodes-base.slack' references\u002Fnode-registry-properties.jsonl\n```\n\nEach line is a JSON object: `{\"node\": \"n8n-nodes-base.slack\", \"properties\": [...]}` where `properties` contains the node's parameter definitions — field names, types, defaults, options, required flags, and conditional display rules. Use this to correctly configure the `parameters` object in `node()` \u002F `trigger()`.\n\n**CRITICAL: Use option `value`, not display `name`.** For `options`-type parameters, the properties JSONL lists both a display `name` (what the UI shows) and the actual `value` (what goes in the workflow JSON). These are often different — you **must** use the `value`. Common traps:\n\n| Node | Parameter | Display Name | Actual Value |\n|------|-----------|-------------|--------------|\n| Set | `mode` | `JSON` | `raw` |\n| HTTP Request | `contentType` | `Form Urlencoded` | `form-urlencoded` |\n| Code | `language` | `Python` | `pythonNative` |\n| Webhook | `responseMode` | `Immediately` | `onReceived` |\n| Merge | `mode` | `SQL Query` | `combineBySql` |\n\n**CRITICAL: Set node v3.3+\u002F3.4 uses `assignments`, NOT `fields`.** The parameter schema changed at v3.3. If you're using Set node v3.4, you MUST use `assignments.assignments` (with `type: 'string'`\u002F`'number'`\u002F`'boolean'` and a single `value` field). The old `fields.values` \u002F `stringValue` \u002F `numberValue` format is for v3.0–3.2 only. See `nodes-and-connections.md` for full examples.\n\n#### Deriving version hints from properties data\n\nWhen the registry cache `version` field appears to be a rounded major version (e.g., `4` instead of `4.4`), you can derive the actual latest version from `@version` hints in the properties JSONL. About 96 of 554 nodes include these hints.\n\nNode properties use `displayOptions.show[\"@version\"]` and `displayOptions.hide[\"@version\"]` to control which fields appear for which versions. These arrays contain the version numbers the node supports, letting you infer the latest version.\n\n**How to extract version hints:**\n\n1. Grep the properties file for the node: `grep 'n8n-nodes-base.set' references\u002Fnode-registry-properties.jsonl`\n2. Search the JSON for all `\"@version\"` values in `displayOptions`\n3. Collect all version numbers — they appear in two forms:\n   - **Explicit lists:** `\"@version\": [3, 3.1, 3.2]` — the node supports versions 3, 3.1, and 3.2\n   - **Conditions:** `\"@version\": [{\"_cnd\": {\"gte\": 3.1}}]` — applies to version ≥ 3.1\n4. The **maximum** value across all explicit numbers and condition thresholds approximates the latest minor version\n\n**Examples from real nodes:**\n- **Set node** — `\"@version\": [3, 3.1, 3.2]` → latest is at least `3.2` (actual defaultVersion: `3.4`)\n- **Agent node** — `\"@version\": [{\"_cnd\": {\"gte\": 3.1}}]` → latest is at least `3.1` (actual: `3.1`)\n- **Switch node** — `\"@version\": [{\"_cnd\": {\"gte\": 3.3}}]` → latest is at least `3.3` (actual: `3.4`)\n\n**Limitations:** This technique only provides a lower bound (the actual `defaultVersion` may be higher). ~458 of 554 nodes have no `@version` hints at all. The registry cache `version` field (updated via `refresh-node-registry.sh` which extracts real `defaultVersion` from npm packages) is always the most accurate source.\n\n### Refreshing the Cache\n\nIf a node can't be found in the cached registries (they may be outdated), **run the refresh script** to pull the latest data from the live APIs:\n\n```bash\nbash scripts\u002Frefresh-node-registry.sh\n```\n\nThis fetches from `https:\u002F\u002Fapi.n8n.io\u002Fapi\u002Fnodes` and `https:\u002F\u002Fapi.n8n.io\u002Fapi\u002Fcommunity-nodes`, installs the `n8n-nodes-base` and `@n8n\u002Fn8n-nodes-langchain` npm packages to extract accurate `defaultVersion` values (the API only returns major versions), strips heavy fields, and updates all three cache files. Run this when you suspect the cache is stale or when a user asks for a node that should exist but isn't in the cache.\n\n### Community Node Warning\n\n**When using a community node**, the user must install the npm package in their n8n instance first. **Always add a `sticky()` note** to the workflow warning about the required community node package:\n\n```typescript\nconst installNote = sticky(\n  '⚠️ Required Community Node: Install the \"@mendable\u002Fn8n-nodes-firecrawl\" package in your n8n instance (Settings → Community Nodes → Install) before using this workflow.',\n  [firecrawlNode],\n  { color: 5 }\n)\n```\n\n### Lookup Workflow\n\n1. **User asks for a specific integration** (e.g., \"add a Slack node\")\n2. **Read `references\u002Fnode-registry-official.json`** and search for the matching node\n3. **If not found in official nodes**, read `references\u002Fnode-registry-community.json` and search there\n4. **If still not found**, run `bash scripts\u002Frefresh-node-registry.sh` to update the cache, then search again\n5. **Use the exact `name` and `version`** from the registry in your `node()` \u002F `trigger()` call\n6. **Grep `references\u002Fnode-registry-properties.jsonl`** for the node name to get its available parameters and configure it correctly\n7. **If using a community node**, add a `sticky()` note listing the required npm package\n8. **If no matching node exists anywhere**, tell the user — do not invent a type\n\n### Quick Reference: Core Utility Nodes\n\nThese fundamental utility node **type names** are always safe to use without a registry lookup. However, **always look up the correct `version`** from `references\u002Fnode-registry-official.json` — do NOT copy version numbers from code examples, as they may be outdated.\n\n| Type | Description |\n|------|-------------|\n| `n8n-nodes-base.manualTrigger` | Manual execution trigger |\n| `n8n-nodes-base.webhook` | HTTP webhook trigger |\n| `n8n-nodes-base.scheduleTrigger` | Cron\u002Finterval trigger |\n| `n8n-nodes-base.httpRequest` | Generic HTTP request |\n| `n8n-nodes-base.set` | Set\u002Ftransform data fields (v3.4 uses `assignments` format — never `fields.values` or `{ options: {} }`) |\n| `n8n-nodes-base.code` | Custom JavaScript\u002FPython code |\n| `n8n-nodes-base.if` | Conditional branching |\n| `n8n-nodes-base.switch` | Multi-branch routing |\n| `n8n-nodes-base.merge` | Merge multiple inputs |\n| `n8n-nodes-base.splitInBatches` | Batch processing loop |\n| `n8n-nodes-base.noOp` | No operation (passthrough) |\n| `n8n-nodes-base.stickyNote` | Canvas annotation |\n| `n8n-nodes-base.respondToWebhook` | Respond to webhook |\n| `n8n-nodes-base.filter` | Filter items |\n| `n8n-nodes-base.executeWorkflow` | Execute sub-workflow |\n| `@n8n\u002Fn8n-nodes-langchain.agent` | AI Agent |\n\n**⚠️ IMPORTANT: Version numbers in code examples throughout this skill are illustrative and may be outdated.** The **only reliable source** for the current version of any node is `references\u002Fnode-registry-official.json` (or `references\u002Fnode-registry-community.json` for community nodes). Always read the registry cache to get the correct `version` before using any node, even core utility nodes listed above.\n\n**For ANY integration node not in this list** (Slack, Gmail, Notion, Airtable, Google Sheets, Postgres, etc.), you MUST look up the correct `type` and `version` from the registry cache before using it.\n\n## Core Concepts\n\nThe SDK provides a **fluent, chainable API** for building n8n workflows in TypeScript\u002FJavaScript. Instead of hand-crafting JSON, you construct workflows programmatically with full type safety, validation, and testing capabilities.\n\n**Key capabilities:**\n1. **Create workflows** — Fluent builder API with `workflow()`, `node()`, `trigger()`\n2. **Import existing workflows** — `workflow.fromJSON(json)` loads any n8n workflow JSON\n3. **Export to JSON** — `.toJSON()` produces n8n-compatible workflow JSON\n4. **Convert JSON to code** — `generateWorkflowCode(json)` produces readable SDK code\n5. **Convert code to JSON** — `parseWorkflowCode(code)` parses SDK code back to JSON\n6. **Validate** — `validateWorkflow()` checks structure, connections, expressions, and best practices\n7. **Test with pin data** — `.generatePinData()` creates mock data from output declarations\n8. **Build AI workflows** — Full support for LangChain subnodes (models, tools, memory, etc.)\n\n## Common Workflows\n\n### Pattern 1: Create a New Workflow from Scratch\n\n```typescript\nimport { workflow, node, trigger, validateWorkflow } from '@n8n\u002Fworkflow-sdk'\n\nconst myTrigger = trigger({ type: 'n8n-nodes-base.manualTrigger', version: 1, config: {} })\nconst httpNode = node({\n  type: 'n8n-nodes-base.httpRequest', version: 4.4,\n  config: {\n    parameters: { url: 'https:\u002F\u002Fapi.example.com\u002Fdata', method: 'GET' },\n    output: [{ json: { id: 1, name: 'Example' } }]\n  }\n})\n\nconst wf = workflow('my-workflow', 'My Workflow')\n  .add(myTrigger)\n  .to(httpNode)\n  .generatePinData()\n\nconst validation = validateWorkflow(wf)\nif (validation.valid) {\n  const json = wf.toJSON()  \u002F\u002F Ready for n8n\n}\n```\n\n### Pattern 2: Load Existing JSON, Modify, Test, Export\n\n```typescript\nimport { workflow, node, generateWorkflowCode, validateWorkflow } from '@n8n\u002Fworkflow-sdk'\n\n\u002F\u002F 1. Load from JSON (user provides their workflow JSON)\nconst wf = workflow.fromJSON(existingWorkflowJSON)\n\n\u002F\u002F 2. Convert to code to understand it\nconst code = generateWorkflowCode(existingWorkflowJSON)\n\n\u002F\u002F 3. Modify — add a new node\nconst newNode = node({\n  type: 'n8n-nodes-base.set', version: 3.4,\n  config: {\n    name: 'Transform',\n    parameters: {\n      mode: 'manual',\n      assignments: {\n        assignments: [\n          { name: 'processed', value: true, type: 'boolean' },\n          { name: 'label', value: '={{ $json.name }}', type: 'string' }\n        ]\n      },\n      includeOtherFields: true,\n      options: {}\n    }\n  }\n})\n\u002F\u002F Rebuild with modifications using the SDK\n\n\u002F\u002F 4. Validate\nconst result = validateWorkflow(wf)\nconsole.log(result.errors, result.warnings)\n\n\u002F\u002F 5. Export back to JSON\nconst updatedJSON = wf.toJSON()\n```\n\n### Pattern 3: Convert JSON to Code, Modify, Convert Back\n\n```typescript\nimport { generateWorkflowCode, parseWorkflowCode, validateWorkflow } from '@n8n\u002Fworkflow-sdk'\n\n\u002F\u002F JSON → Code (for reading\u002Funderstanding)\nconst code = generateWorkflowCode(workflowJSON)\n\n\u002F\u002F Modify the code as needed...\n\n\u002F\u002F Code → JSON (for importing back to n8n)\nconst newJSON = parseWorkflowCode(modifiedCode)\n\n\u002F\u002F Validate before deploying\nconst result = validateWorkflow(newJSON)\n```\n\n### Pattern 4: Build an AI Agent Workflow\n\n```typescript\nimport { workflow, node, trigger, languageModel, tool, memory, fromAi } from '@n8n\u002Fworkflow-sdk'\n\nconst model = languageModel({\n  type: '@n8n\u002Fn8n-nodes-langchain.lmChatOpenAi', version: 1.3,\n  config: {\n    parameters: {\n      model: { __rl: true, mode: 'list', value: 'gpt-4o-mini', cachedResultName: 'gpt-4o-mini' },\n      options: {}\n    },\n    credentials: { openAiApi: { name: 'OpenAI', id: 'cred-123' } }\n  }\n})\n\nconst emailTool = tool({\n  type: '@n8n\u002Fn8n-nodes-langchain.toolGmail', version: 1,\n  config: {\n    parameters: {\n      recipient: fromAi('recipient', 'Email recipient address'),\n      subject: fromAi('subject', 'Email subject line')\n    }\n  }\n})\n\nconst agent = node({\n  type: '@n8n\u002Fn8n-nodes-langchain.agent', version: 3.1,\n  config: {\n    name: 'AI Agent',\n    parameters: {\n      promptType: 'define',\n      text: '={{ $json.chatInput }}',\n      options: { systemMessage: 'You are a helpful AI assistant.' }\n    },\n    subnodes: { model, tools: [emailTool], memory: memoryNode }\n  }\n})\n\nconst wf = workflow('ai-workflow', 'AI Agent Workflow')\n  .add(trigger({ type: 'n8n-nodes-base.manualTrigger', version: 1, config: {} }))\n  .to(agent)\n```\n\n**⚠️ Model IDs in examples are illustrative and will become outdated.** Always choose the most appropriate **current** model for the user's use case — do NOT copy model IDs like `gpt-4o-mini` from these examples. Check the provider's latest model offerings.\n\n## SDK API Quick Reference\n\n### Factory Functions\n\n| Function | Purpose | Reference |\n|----------|---------|-----------|\n| `workflow(id, name, options?)` | Create workflow builder | `references\u002Fworkflow-building.md` |\n| `workflow.fromJSON(json)` | Import from n8n JSON | `references\u002Fworkflow-building.md` |\n| `node(input)` | Create a regular node | `references\u002Fnodes-and-connections.md` |\n| `trigger(input)` | Create a trigger node | `references\u002Fnodes-and-connections.md` |\n| `sticky(content, nodes?, config?)` | Create a sticky note | `references\u002Fnodes-and-connections.md` |\n| `placeholder(hint)` | Create a placeholder value | `references\u002Fnodes-and-connections.md` |\n| `newCredential(name)` | Mark credential as new | `references\u002Fnodes-and-connections.md` |\n| `ifElse(config)` | Create IF node | `references\u002Fcontrol-flow.md` |\n| `switchCase(config)` | Create Switch node | `references\u002Fcontrol-flow.md` |\n| `merge(config)` | Create Merge node | `references\u002Fcontrol-flow.md` |\n| `splitInBatches(config)` | Create Split In Batches | `references\u002Fcontrol-flow.md` |\n| `nextBatch(sib)` | Loop-back for SIB | `references\u002Fcontrol-flow.md` |\n\n### Subnode Factories (AI\u002FLangChain)\n\n| Function | Purpose |\n|----------|---------|\n| `languageModel(input)` | Language model (OpenAI, Anthropic, etc.) |\n| `memory(input)` | Memory (Buffer Window, etc.) |\n| `tool(input)` | Tool (Calculator, Code, Gmail, etc.) |\n| `outputParser(input)` | Output parser |\n| `embedding(input)` \u002F `embeddings(input)` | Embedding model |\n| `vectorStore(input)` | Vector store (Pinecone, Qdrant, etc.) |\n| `retriever(input)` | Retriever |\n| `documentLoader(input)` | Document loader |\n| `textSplitter(input)` | Text splitter |\n| `fromAi(key, description?, type?, defaultValue?)` | AI-driven parameter |\n\n### Expression Utilities\n\n| Function | Purpose |\n|----------|---------|\n| `serializeExpression(fn)` | `$ => $.json.name` → `={{ $json.name }}` |\n| `parseExpression(expr)` | `={{ $json.name }}` → `$json.name` |\n| `isExpression(value)` | Check if string is an expression |\n| `expr(expression)` | Mark string as expression (adds `=` prefix) |\n\n### Code Helpers\n\n| Function | Purpose |\n|----------|---------|\n| `runOnceForAllItems(fn)` | Code node: process all items at once |\n| `runOnceForEachItem(fn)` | Code node: process one item at a time |\n\n### Validation & Testing\n\n| Function | Purpose |\n|----------|---------|\n| `validateWorkflow(wf, options?)` | Validate workflow structure |\n| `.generatePinData(options?)` | Generate pin\u002Ftest data from outputs |\n| `.validate(options?)` | Validate on the builder directly |\n\n### Code Generation\n\n| Function | Purpose |\n|----------|---------|\n| `generateWorkflowCode(json)` | JSON → TypeScript SDK code |\n| `parseWorkflowCode(code)` | SDK code → JSON |\n| `parseWorkflowCodeToBuilder(code)` | SDK code → WorkflowBuilder |\n\n### WorkflowBuilder Methods\n\n| Method | Purpose |\n|--------|---------|\n| `.add(node)` | Add node to workflow |\n| `.to(target)` | Connect current node to target |\n| `.connect(src, srcOut, tgt, tgtIn)` | Explicit indexed connection |\n| `.settings(settings)` | Set workflow settings |\n| `.getNode(name)` | Get node by name |\n| `.validate(options?)` | Validate the workflow |\n| `.toJSON()` | Export as n8n JSON |\n| `.toFormat(format)` | Serialize with plugin |\n| `.generatePinData(options?)` | Generate test data |\n| `.regenerateNodeIds()` | Regenerate deterministic IDs |\n\n### NodeInstance Methods\n\n| Method | Purpose |\n|--------|---------|\n| `.to(target, outputIndex?)` | Connect to next node(s) |\n| `.input(index)` | Create input target (for Merge) |\n| `.output(index)` | Select specific output index |\n| `.onTrue(target)` | IF node: true branch |\n| `.onFalse(target)` | IF node: false branch |\n| `.onCase(index, target)` | Switch node: case branch |\n| `.onError(handler)` | Set error handler |\n| `.update(config)` | Create updated copy |\n| `.getConnections()` | Get declared connections |\n\n## Decision Guide\n\n### When to Use Each Pattern\n\n**Creating a brand-new workflow:**\n1. **Look up node types** — Read `references\u002Fnode-registry-official.json` (and `references\u002Fnode-registry-community.json` if needed) to get the correct `type` and `version` for every integration node\n2. Design the flow (trigger → processing → output)\n3. Create nodes with `node()` and `trigger()` using the real types from the registry\n4. If using community nodes, add `sticky()` notes listing required npm packages\n5. Chain with `.add()` and `.to()`\n6. Add output declarations for testing\n7. Validate with `validateWorkflow()`\n8. Export with `.toJSON()`\n\n**Modifying an existing n8n workflow (JSON):**\n1. Load with `workflow.fromJSON(json)`\n2. Optionally convert to code with `generateWorkflowCode(json)` to understand it\n3. Build the modified version using the SDK\n4. Validate the new version\n5. Export with `.toJSON()`\n\n**Understanding a complex workflow:**\n1. Convert with `generateWorkflowCode(json)` — produces readable TypeScript\n2. Read the generated code to understand the flow\n\n**Testing\u002Fverifying a workflow:**\n1. Build or load the workflow\n2. Add `output` declarations to nodes (expected output shapes)\n3. Call `.generatePinData()` to create test data\n4. Call `validateWorkflow()` to check for errors\n5. Review validation errors and warnings\n\n**Round-tripping (code ↔ JSON):**\n- JSON → Code: `generateWorkflowCode(json)`\n- Code → JSON: `parseWorkflowCode(code)`\n- Code → Builder: `parseWorkflowCodeToBuilder(code)` (allows validation before JSON export)\n\n## Connection Patterns Summary\n\n```typescript\n\u002F\u002F Sequential: A → B → C\nworkflow('id', 'name').add(a).to(b).to(c)\n\n\u002F\u002F Fan-out: A → [B, C, D] (parallel branches, output indices 0, 1, 2)\nworkflow('id', 'name').add(a).to([b, c, d])\n\n\u002F\u002F Multi-input (Merge): A → merge.input(0), B → merge.input(1)\nworkflow('id', 'name')\n  .add(a).to(merge.input(0))\n  .add(b).to(merge.input(1))\n\n\u002F\u002F Explicit indexed: source output 0 → target input 1\nworkflow('id', 'name').connect(source, 0, target, 1)\n\n\u002F\u002F Output selection: use specific output of a multi-output node\nnodeA.output(0).to(nodeB)\nnodeA.output(1).to(nodeC)\n\n\u002F\u002F IF branching\nifNode.onTrue(trueNode).onFalse(falseNode)\n\n\u002F\u002F Switch branching\nswitchNode.onCase(0, caseA).onCase(1, caseB).onCase(2, caseC)\n\n\u002F\u002F Error handling\nsourceNode.onError(errorHandler)\n\n\u002F\u002F Split in Batches loop\nsib.onEachBatch(process.to(nextBatch(sib))).onDone(finalNode)\n```\n\n## Validation Error Codes\n\nThe SDK validates for 23+ error conditions. Key ones to watch for:\n\n| Code | Meaning |\n|------|---------|\n| `NO_NODES` | Workflow has no nodes |\n| `MISSING_TRIGGER` | No trigger node found |\n| `DISCONNECTED_NODE` | Node not connected to flow |\n| `INVALID_CONNECTION` | Invalid connection between nodes |\n| `MISSING_PARAMETER` | Required parameter missing |\n| `CIRCULAR_REFERENCE` | Circular connection detected |\n| `INVALID_EXPRESSION` | Malformed expression |\n| `AGENT_STATIC_PROMPT` | AI agent has static prompt |\n| `HARDCODED_CREDENTIALS` | Credentials hardcoded in parameters |\n| `MERGE_SINGLE_INPUT` | Merge node has only one input |\n| `TOOL_NO_PARAMETERS` | Tool has no AI-driven parameters |\n| `FROM_AI_IN_NON_TOOL` | `fromAi()` used outside a tool node |\n| `SUBNODE_NOT_CONNECTED` | Subnode not connected to parent |\n\nSee `references\u002Fvalidation-and-testing.md` for the complete list and resolution strategies.\n\n## Node Type Constants\n\n```typescript\nimport { NODE_TYPES } from '@n8n\u002Fworkflow-sdk'\n\nNODE_TYPES.IF                  \u002F\u002F 'n8n-nodes-base.if'\nNODE_TYPES.SWITCH              \u002F\u002F 'n8n-nodes-base.switch'\nNODE_TYPES.MERGE               \u002F\u002F 'n8n-nodes-base.merge'\nNODE_TYPES.STICKY_NOTE         \u002F\u002F 'n8n-nodes-base.stickyNote'\nNODE_TYPES.SPLIT_IN_BATCHES    \u002F\u002F 'n8n-nodes-base.splitInBatches'\nNODE_TYPES.HTTP_REQUEST        \u002F\u002F 'n8n-nodes-base.httpRequest'\nNODE_TYPES.WEBHOOK             \u002F\u002F 'n8n-nodes-base.webhook'\nNODE_TYPES.DATA_TABLE          \u002F\u002F 'n8n-nodes-base.dataTable'\n```\n\n## Type Guards\n\n```typescript\nimport {\n  isIfNodeType, isSwitchNodeType, isMergeNodeType,\n  isStickyNoteType, isSplitInBatchesType, isHttpRequestType,\n  isWebhookType, isDataTableType, isNodeChain, isNodeInstance\n} from '@n8n\u002Fworkflow-sdk'\n```\n\n## Scope\n\nThis skill is for **building and manipulating n8n workflows programmatically** using the `@n8n\u002Fworkflow-sdk`. It covers workflow creation, JSON import\u002Fexport, validation, code generation, pin data, and the full SDK API.\n\n**This skill is NOT for:**\n- Deploying workflows to n8n instances or managing n8n server configuration\n- Building custom n8n community nodes (use the `n8n-node-builder` skill instead)\n- Using the n8n UI or REST API directly\n- Managing n8n credentials in a running instance\n\n## Best Practices\n\n1. **NEVER guess node types or versions — always look them up** — Read `references\u002Fnode-registry-official.json` (official) or `references\u002Fnode-registry-community.json` (community) to get the real `type` and `version`. Do NOT copy version numbers from code examples in this skill — they are illustrative and may be outdated. The registry cache is the single source of truth for versions. A wrong node type or version produces a broken workflow.\n2. **Flag community nodes with sticky notes** — If the workflow uses community nodes, add a `sticky()` note listing the npm packages the user must install in their n8n instance.\n3. **Always validate before exporting** — Call `validateWorkflow()` before `.toJSON()`. The SDK catches 23+ error conditions that would silently produce broken workflows in n8n, so skipping validation means shipping bugs.\n4. **Use output declarations for testing** — Add `output` to node configs, then call `generatePinData()`. Without output declarations, there's no way to generate test fixtures automatically, and downstream nodes can't be tested with realistic data shapes.\n5. **Use `generateWorkflowCode()` to understand existing workflows** — Raw n8n JSON is verbose and hard to follow. The generated TypeScript code reveals the logical flow, branching, and node relationships at a glance.\n6. **Use `parseWorkflowCode()` for round-tripping** — Code → JSON → Code round-trips cleanly, making it safe to convert, edit in code, and convert back without data loss.\n7. **Use `parseWorkflowCodeToBuilder()` when you need validation** — Unlike `parseWorkflowCode()` which returns raw JSON, this returns a WorkflowBuilder so you can `.validate()` before `.toJSON()`, catching errors before they reach n8n.\n8. **Use credentials objects, not hardcoded values** — Hardcoded API keys in parameters are a security risk and won't work across environments. The validator catches `HARDCODED_CREDENTIALS` to prevent this.\n9. **Use `fromAi()` only in tool nodes** — `fromAi()` generates `$fromAI` expressions that only the AI Agent node can resolve. Using them in regular nodes produces expressions that will fail at runtime. The validator catches `FROM_AI_IN_NON_TOOL`.\n10. **Use `expr()` for dynamic values** — Raw strings with `={{ }}` are error-prone (missing `=` prefix, unbalanced braces). `expr()` handles the prefix automatically and makes intent clear in code.\n11. **Set `output` on nodes to enable pin data generation** — This is how you create test fixtures. Without declared outputs, `generatePinData()` has nothing to convert.\n12. **Use `sticky()` for workflow documentation** — Auto-positions around given nodes, making it easy to annotate sections of complex workflows for other developers.\n13. **NEVER generate empty Set nodes** — A Set node with `parameters: { options: {} }` is broken and does nothing. Every Set node v3.4 MUST have `mode: 'manual'` with `assignments.assignments` containing at least one entry, OR `mode: 'raw'` with a `jsonOutput` string. Use `type: 'string'`\u002F`'number'`\u002F`'boolean'` and a single `value` field — NOT the old `stringValue`\u002F`numberValue` format. If you don't know what fields to set, ask the user.\n14. **Use current AI models, not example model IDs** — Model IDs in code examples (e.g., `gpt-4o-mini`, `claude-sonnet-4-20250514`) are illustrative and become outdated. Always select the most appropriate current model for the user's task. Check the provider's latest offerings rather than copying from examples.\n\n",{"name":5,"description":7},"main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fgeckse\u002Fn8n-skills\u002Fn8n-workflow-sdk",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":78},[68,71,74],{"tool":69,"command":70},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill n8n-workflow-sdk",{"tool":72,"command":73},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill n8n-workflow-sdk",{"tool":75,"command":76},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill n8n-workflow-sdk","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fgeckse\u002Fn8n-skills\u002Fn8n-workflow-sdk\u002FSKILL.md",true,[],[81,91,101,109,115,122],{"skillNo":82,"name":83,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":87,"publishedAt":52},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[86],{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[88,89,90],"model","model hierarchy","model hierarchy skill",{"skillNo":92,"name":93,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":97,"publishedAt":52},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[96],{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[98,99,100],"MCP","zoom","zoom mcp",{"skillNo":102,"name":103,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":106,"publishedAt":52},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[36,107,108,99],"zoom apps sdk","apps",{"skillNo":110,"name":111,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":52},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[111],{"skillNo":116,"name":117,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":118,"categories":119,"keywords":121,"publishedAt":52},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[120],{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},[117],{"skillNo":123,"name":124,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":127,"publishedAt":52},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[124]]