n8nac — Code-First n8n Workflow Development
n8nac (@n8n-as-code/cli) manages n8n workflows as clean, version-controlled TypeScript files using decorators. This skill covers the universal protocol — project-specific config is generated by n8nac update-ai.
Based on n8nac v1.6.x protocol. Run
npx --yes n8nac update-aiin your project for the latest project-specific AGENTS.md.
Drift check: This skill captures the stable universal protocol. If n8nac has been updated since v1.6.x, run
npx --yes n8nac update-aiand compare the generated AGENTS.md against this skill. If the protocol changed (new commands, renamed flags, new error classes), update this skill to match.
Workspace Bootstrap (MANDATORY)
Before ANY n8nac command, the workspace must be initialized.
Check
- Look for
n8nac-config.jsonat workspace root - If missing or incomplete (no
projectId/projectName): not initialized
Initialize (2-step non-interactive)
# Step 1: Save credentials
npx --yes n8nac init-auth --host <url> --api-key <key>
# Step 2: Select project
npx --yes n8nac init-project --project-index 1 --sync-folder workflows
1-command alternative (when project is known)
npx --yes n8nac init --yes --host <url> --api-key <key> --project-index 1 --sync-folder workflows
Environment variables
If N8N_HOST and N8N_API_KEY are set in the shell, use them:
npx --yes n8nac init-auth --host "$N8N_HOST" --api-key "$N8N_API_KEY"
Instance management
npx --yes n8nac instance list --json # List saved configs
npx --yes n8nac instance select --instance-name <name> # Switch
npx --yes n8nac instance delete --instance-name <name> --yes # Remove
Rules:
- Never tell the user to run init — the agent runs it
- Never write
n8nac-config.jsonby hand - Never run list/pull/push before init completes
- Don't assume init happened just because workflow files exist
GitOps Sync Protocol (CRITICAL)
n8nac uses a Git-like sync architecture. Local code is source of truth, but the user might have edited in n8n UI.
The 7-Step Workflow
1. LIST — Check status
npx --yes n8nac list # All workflows with sync status
npx --yes n8nac list --local # Local .workflow.ts files only
npx --yes n8nac list --remote # Remote workflows only
2. PULL — Download remote changes before editing
npx --yes n8nac pull <workflowId>
Required if remote has newer changes. Skip = OCC rejection on push.
3. EDIT/CREATE — Work on local .workflow.ts
- Existing: edit the pulled file
- New: create in the
workflowDirfromn8nac-config.json(the active instance's canonical path) - Confirm with
npx --yes n8nac list --localbefore pushing
4. PUSH — Upload to n8n
npx --yes n8nac push <path> # Full or workspace-relative path
npx --yes n8nac push <path> --verify # Push + verify in one step
Path rules:
- Always use full path including
.workflow.tssuffix - Use absolute or workspace-root-relative path (e.g.,
workflows/instance/project/my-workflow.workflow.ts) - Never bare filename, never omit extension, never use workflow title
5. VERIFY — Validate live workflow
npx --yes n8nac verify <workflowId>
Catches: invalid typeVersion, bad operation values, missing required params, unknown node types.
6. TEST-PLAN — Check testability
npx --yes n8nac test-plan <workflowId> # Human readable
npx --yes n8nac test-plan <workflowId> --json # Structured for agents
7. TEST — Execute webhook/chat/form workflows
# STANDARD sequence (ALWAYS use this):
npx --yes n8nac workflow activate <workflowId>
npx --yes n8nac test <workflowId> --prod
# With custom payload:
npx --yes n8nac test <workflowId> --prod --data '{"key":"value"}'
Default rule: ALWAYS activate first, ALWAYS use --prod. Bare test <id> requires manual arm in n8n editor.
8. RESOLVE — Handle conflicts
npx --yes n8nac resolve <id> --mode keep-current # Force local
npx --yes n8nac resolve <id> --mode keep-incoming # Force remote
Error Classification
n8nac test classifies failures into three buckets:
| Class | Exit Code | Action |
|---|---|---|
| Class A — Config gap | 0 | Missing credentials/model/env var. Inform user, do NOT re-edit code |
| Runtime state | 0 | Webhook not armed, production webhook not registered. Fix state, NOT code |
| Class B — Wiring error | 1 | Bad expression, wrong field. Fix .workflow.ts, push, re-test |
Critical: A Class A error is NOT a bug. Never push/edit to fix missing credentials.
Research Protocol (MANDATORY before creating/editing nodes)
Step 0: Pattern Discovery
npx --yes n8nac skills examples search "telegram chatbot"
Step 1: Search for the node
npx --yes n8nac skills search "google sheets"
Step 2: Get exact schema
npx --yes n8nac skills node-info googleSheets # Complete
npx --yes n8nac skills node-schema googleSheets # Quick reference
Step 3: Apply schema as absolute truth
- Use EXACT
typefrom schema (with full package prefix) - Use HIGHEST
typeVersionfrom schema - Use exact parameter names
Step 4: Validate before push
npx --yes n8nac skills validate workflow.workflow.ts
Step 5: Verify after push
npx --yes n8nac verify <workflowId>
TypeScript Decorator Syntax
Minimal workflow structure
import { workflow, node, links } from '@n8n-as-code/transformer';
@workflow({ name: 'Workflow Name', active: false })
export class MyWorkflow {
@node({
name: 'Descriptive Name',
type: '/* EXACT from search */',
version: 4,
position: [250, 300]
})
MyNode = { /* parameters from node-info */ };
@links()
defineRouting() {
this.MyNode.out(0).to(this.NextNode.in(0));
}
}
AI Agent pattern (LangChain nodes)
@workflow({ name: 'AI Agent', active: false })
export class AIAgentWorkflow {
@node({ name: 'Chat Trigger', type: '@n8n/n8n-nodes-langchain.chatTrigger', version: 1.4 })
ChatTrigger = {};
@node({ name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', version: 3.1 })
AiAgent = {
promptType: 'define',
text: '={{ $json.chatInput }}',
hasOutputParser: true,
options: { systemMessage: 'You are a helpful assistant.' },
};
@node({ name: 'Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', version: 1.3,
credentials: { openAiApi: { id: 'xxx', name: 'OpenAI' } } })
Model = { model: { mode: 'list', value: 'gpt-4o-mini' }, options: {} };
@links()
defineRouting() {
this.ChatTrigger.out(0).to(this.AiAgent.in(0));
// AI sub-nodes MUST use .uses(), NEVER .out().to()
this.AiAgent.uses({
ai_languageModel: this.Model.output, // single ref
ai_tool: [this.SearchTool.output], // array ref (tools, documents)
});
}
}
Key rule: Regular nodes: .out(0).to(target.in(0)). AI sub-nodes (models, memory, tools, parsers): .uses() only.
Workflow Map Navigation
Every .workflow.ts starts with a <workflow-map> comment block — a compact index. Read this FIRST, then search for the specific property name you need. Never load the entire file into context.
13 Common Mistakes
- Wrong node type — missing package prefix (
switchvsn8n-nodes-base.switch) - Outdated typeVersion — always use highest from schema
- Non-existent typeVersion — verify against exact array in node-schema
- Invalid operation value — check exact string in options[].value list
- Mismatched resource + operation — each resource has different valid operations
- Guessing parameter structure — always check schema for nested objects
- Wrong connection names — must match exact node
namefield - Inventing non-existent nodes — use
searchto verify - Wrong
.uses()syntax —ai_tool/ai_documentare ALWAYS arrays; all others single refs - Connecting AI sub-nodes with
.out().to()— use.uses()for anything flagged[ai_*] - Guessing fixedCollection values — always run
node-infofirst - Inverting value1/value2 in Switch/If — value1 = expression, value2 = literal
- Wrong formFields structure for Wait — use
{ values: [...] }, notformFieldsUi.fieldItems
Additional Tools
# Execution inspection (debug post-trigger)
npx --yes n8nac execution list --workflow-id <id> --limit 5 --json
npx --yes n8nac execution get <execId> --include-data --json
# Credential management (resolve Class A without UI)
npx --yes n8nac workflow credential-required <id> --json
npx --yes n8nac credential schema <type>
npx --yes n8nac credential list --json
npx --yes n8nac credential create --type <type> --name <name> --file cred.json --json
# Workflow lifecycle
npx --yes n8nac workflow activate <id>
npx --yes n8nac workflow deactivate <id>
# Documentation
npx --yes n8nac skills docs "OpenAI"
npx --yes n8nac skills guides "webhook"
When in doubt:
npx --yes n8nac skills node-info <nodeName>— the schema is always the source of truth.