AgentStack
SKILL verified MIT Self-run

N8n Syntax Trigger Nodes

skill-impertio-studio-n8n-claude-skill-package-n8n-syntax-trigger-nodes · by Impertio-Studio

>

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add skill-impertio-studio-n8n-claude-skill-package-n8n-syntax-trigger-nodes

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of N8n Syntax Trigger Nodes? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

n8n Trigger Node Development

> Build custom trigger nodes for n8n v1.x using event/timer, polling, or webhook patterns.

Quick Reference

| Pattern | Method | Interface | Description Key | Use When | |---------|--------|-----------|-----------------|----------| | Event/Timer | trigger() | ITriggerFunctions | group: ['trigger'] | Listening to events, cron schedules, streams | | Polling | poll() | IPollFunctions | polling: true | Periodically checking an API for new data | | Webhook | webhook() | IWebhookFunctions | webhooks: [...] | External service sends HTTP requests to n8n |

Critical Rules

  • ALWAYS set inputs: [] on trigger nodes — triggers have NO inputs
  • ALWAYS include 'trigger' in the group array
  • ALWAYS double-wrap emitted data: this.emit([[items]]) — outer array = outputs, inner array = items
  • ALWAYS provide manualTriggerFunction for event triggers so "Test Workflow" works in the editor
  • ALWAYS implement closeFunction when your trigger allocates resources (intervals, connections, listeners)
  • NEVER emit single-wrapped arrays — this.emit([items]) causes silent failures
  • NEVER forget cleanup — leaked intervals/connections persist across workflow deactivation

Decision Tree: Which Trigger Pattern?

Need a trigger node?
├── Does an EXTERNAL SERVICE call YOUR endpoint?
│   └── YES → Use WEBHOOK pattern (webhook() + IWebhookFunctions)
│       ├── Service has webhook registration API? → Add webhookMethods lifecycle
│       └── Service sends to a static URL? → Use webhooks[] description only
├── Do you need to CHECK an API periodically?
│   └── YES → Use POLLING pattern (poll() + IPollFunctions)
│       └── ALWAYS implement deduplication (track last seen ID/timestamp)
└── Do you LISTEN for events, run on schedule, or stream data?
    └── YES → Use EVENT/TIMER pattern (trigger() + ITriggerFunctions)
        └── ALWAYS implement closeFunction for cleanup

Pattern 1: Event/Timer Trigger

Use trigger() with ITriggerFunctions for schedule-based or event-driven triggers.

Minimal Structure

import type {
    ITriggerFunctions,
    INodeType,
    INodeTypeDescription,
    ITriggerResponse,
    INodeExecutionData,
} from 'n8n-workflow';
import { NodeConnectionTypes } from 'n8n-workflow';

export class MyEventTrigger implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'My Event Trigger',
        name: 'myEventTrigger',
        group: ['trigger'],          // REQUIRED for trigger nodes
        version: 1,
        inputs: [],                  // ALWAYS empty for triggers
        outputs: [NodeConnectionTypes.Main],
        defaults: { name: 'My Event Trigger' },
        properties: [/* ... */],
    };

    async trigger(this: ITriggerFunctions): Promise {
        const executeTrigger = () => {
            // CRITICAL: Double-wrap — [[items]]
            this.emit([[ { json: { timestamp: new Date().toISOString() } } ]]);
        };

        if (this.getMode() === 'manual') {
            // Editor "Test Workflow" mode
            return { manualTriggerFunction: async () => executeTrigger() };
        }

        // Production mode — set up persistent listener
        const intervalId = setInterval(executeTrigger, 60_000);

        // ALWAYS return closeFunction for cleanup
        return { closeFunction: async () => clearInterval(intervalId) };
    }
}

ITriggerResponse Fields

| Field | Type | Purpose | |-------|------|---------| | closeFunction | () => Promise | Cleanup when workflow deactivated. ALWAYS implement when allocating resources. | | manualTriggerFunction | () => Promise | Simulates trigger for "Test Workflow" in editor. ALWAYS implement for event triggers. | | manualTriggerResponse | Promise | Alternative: promise that resolves when data is emitted. |

Emitting Data

// CORRECT — double array wrapping
this.emit([[ { json: { key: 'value' } } ]]);

// CORRECT — multiple items in one emission
this.emit([[ { json: { id: 1 } }, { json: { id: 2 } } ]]);

// CORRECT — using helper for array conversion
this.emit([this.helpers.returnJsonArray([{ id: 1 }, { id: 2 }])]);

// WRONG — single array wrapping (SILENT FAILURE)
this.emit([{ json: { key: 'value' } }]);  // DO NOT DO THIS

Error Reporting

// Non-fatal: log error, workflow stays active
this.saveFailedExecution(error);

// Fatal: deactivates workflow, queues for reactivation with backoff
this.emitError(error);

Pattern 2: Polling Trigger

Use poll() with IPollFunctions for periodic API checks.

Minimal Structure

export class MyPollingTrigger implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'My Polling Trigger',
        name: 'myPollingTrigger',
        group: ['trigger'],
        version: 1,
        polling: true,               // REQUIRED — marks as polling trigger
        inputs: [],
        outputs: [NodeConnectionTypes.Main],
        defaults: { name: 'My Polling Trigger' },
        properties: [
            {
                displayName: 'Poll Interval',
                name: 'pollInterval',
                type: 'options',
                default: 'every5Minutes',
                options: [
                    { name: 'Every Minute', value: 'everyMinute' },
                    { name: 'Every 5 Minutes', value: 'every5Minutes' },
                ],
            },
        ],
    };

    async poll(this: IPollFunctions): Promise {
        // Return null when no new data — NEVER return empty arrays
        // Return [[items]] when new data found
    }
}

Deduplication Pattern

ALWAYS implement deduplication for polling triggers to prevent duplicate processing.

async poll(this: IPollFunctions): Promise {
    const webhookData = this.getWorkflowStaticData('node');
    const lastTimestamp = webhookData.lastTimestamp as string | undefined;

    const items = await fetchNewItems(lastTimestamp);

    if (items.length === 0) {
        return null;  // ALWAYS return null for "no new data"
    }

    // Update stored state for next poll cycle
    webhookData.lastTimestamp = items[items.length - 1].updatedAt;

    return [items.map(item => ({ json: item }))];
}

> Key: Use this.getWorkflowStaticData('node') to persist state between poll cycles. This data survives workflow restarts. ALWAYS update the stored marker after processing.

Pattern 3: Webhook Trigger

Use webhook() with IWebhookFunctions for HTTP-triggered workflows.

Minimal Structure

export class MyWebhookTrigger implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'My Webhook Trigger',
        name: 'myWebhookTrigger',
        group: ['trigger'],
        version: 1,
        inputs: [],
        outputs: [NodeConnectionTypes.Main],
        defaults: { name: 'My Webhook Trigger' },
        webhooks: [
            {
                name: 'default',
                httpMethod: '={{$parameter["httpMethod"] || "POST"}}',
                path: '={{$parameter["path"]}}',
                responseMode: '={{$parameter["responseMode"]}}',
            },
        ],
        properties: [/* httpMethod, path, responseMode */],
    };

    async webhook(this: IWebhookFunctions): Promise {
        const body = this.getBodyData();
        return { workflowData: [[ { json: body } ]] };
    }
}

WebhookResponseMode

| Mode | Behavior | Use When | |------|----------|----------| | onReceived | Respond immediately after webhook node executes | Fire-and-forget; caller does not need workflow result | | lastNode | Respond after the LAST node in workflow finishes | Caller needs the final processed result | | responseNode | Respond from a dedicated "Respond to Webhook" node | Need custom response body/status at a specific point |

IWebhookFunctions Methods

| Method | Returns | Purpose | |--------|---------|---------| | getBodyData() | IDataObject | Parsed request body | | getHeaderData() | IncomingHttpHeaders | Request headers | | getQueryData() | object | URL query parameters | | getParamsData() | object | URL path parameters | | getRequestObject() | express.Request | Full Express request (advanced) | | getResponseObject() | express.Response | Full Express response (advanced, for manual response) | | getNodeWebhookUrl('default') | string | The registered webhook URL |

Webhook Lifecycle (webhookMethods)

For triggers that register webhooks on EXTERNAL services (e.g., GitHub, Stripe):

webhookMethods: {
    default: {
        async checkExists(this: IHookFunctions): Promise {
            // Return true if webhook already registered on external service
        },
        async create(this: IHookFunctions): Promise {
            // Register webhook URL on external service
            // Store webhook ID in static data for later deletion
            const webhookUrl = this.getNodeWebhookUrl('default');
            // ... API call to register ...
            return true;
        },
        async delete(this: IHookFunctions): Promise {
            // Remove webhook from external service on deactivation
            return true;
        },
    },
},

Lifecycle: checkExists runs first. If false, create runs. On workflow deactivation, delete runs.

IWebhookResponseData Fields

| Field | Type | Purpose | |-------|------|---------| | workflowData | INodeExecutionData[][] | Data passed into the workflow | | webhookResponse | any | Custom response sent back to the caller | | noWebhookResponse | boolean | Set true if you already sent a response via getResponseObject() |

Trigger Node Description Requirements

Every trigger node description MUST include:

description: INodeTypeDescription = {
    group: ['trigger'],              // REQUIRED — identifies as trigger
    inputs: [],                      // REQUIRED — triggers have no inputs
    outputs: [NodeConnectionTypes.Main],
    // For polling triggers, add:
    polling: true,
    // For webhook triggers, add:
    webhooks: [{ name: 'default', httpMethod: '...', path: '...', responseMode: '...' }],
    // Optional but recommended:
    eventTriggerDescription: 'Waiting for events from MyService',
    activationMessage: 'Your trigger is now active and listening.',
};

Reference Files

| File | Contents | |------|----------| | [references/methods.md](references/methods.md) | Complete ITriggerFunctions, IWebhookFunctions, IPollFunctions, ITriggerResponse, webhookMethods, WebhookResponseMode | | [references/examples.md](references/examples.md) | Full implementations: schedule trigger, webhook trigger, polling trigger | | [references/anti-patterns.md](references/anti-patterns.md) | Common trigger implementation mistakes and fixes |

Sources

  • n8n-io/n8n GitHub: packages/workflow/src/interfaces.ts — ITriggerFunctions, IWebhookFunctions, IPollFunctions, ITriggerResponse
  • n8n-io/n8n GitHub: packages/nodes-base/nodes/Schedule/ScheduleTrigger.node.ts
  • n8n-io/n8n GitHub: packages/nodes-base/nodes/Webhook/Webhook.node.ts
  • n8n official docs: https://docs.n8n.io/integrations/creating-nodes/build/programmatic-style/

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.