| name | n8n-syntax-trigger-nodes |
| description | Use when creating custom trigger nodes or implementing webhook/polling handlers in n8n v1.x. Prevents incorrect trigger lifecycle implementation by missing closeFunction or manualTriggerFunction. Covers three trigger patterns (event/timer, polling, webhook), ITriggerFunctions, IWebhookFunctions, IPollFunctions interfaces, ITriggerResponse, webhook lifecycle methods (checkExists/create/delete), and WebhookResponseMode types. Keywords: n8n, trigger node, webhook, polling, ITriggerFunctions, schedule, cron, every Monday, daily, hourly, recurring, start workflow automatically, polling..
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires n8n v1.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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'],
version: 1,
inputs: [],
outputs: [NodeConnectionTypes.Main],
defaults: { name: 'My Event Trigger' },
properties: [],
};
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
const executeTrigger = () => {
.([[ { : { : ().() } } ]]);
};
(.() === ) {
{ : () => () };
}
intervalId = (executeTrigger, );
{ : () => (intervalId) };
}
}
ITriggerResponse Fields
| Field | Type | Purpose |
|---|
closeFunction | () => Promise<void> | Cleanup when workflow deactivated. ALWAYS implement when allocating resources. |
manualTriggerFunction | () => Promise<void> | Simulates trigger for "Test Workflow" in editor. ALWAYS implement for event triggers. |
manualTriggerResponse | Promise<INodeExecutionData[][]> | Alternative: promise that resolves when data is emitted. |
Emitting Data
this.emit([[ { json: { key: 'value' } } ]]);
this.emit([[ { json: { id: 1 } }, { json: { id: 2 } } ]]);
this.emit([this.helpers.returnJsonArray([{ id: 1 }, { id: 2 }])]);
this.emit([{ json: { key: 'value' } }]);
Error Reporting
this.saveFailedExecution(error);
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,
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<INodeExecutionData[][] | > {
}
}
Deduplication Pattern
ALWAYS implement deduplication for polling triggers to prevent duplicate processing.
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const webhookData = this.getWorkflowStaticData('node');
const lastTimestamp = webhookData.lastTimestamp as string | undefined;
const items = await fetchNewItems(lastTimestamp);
if (items.length === 0) {
return null;
}
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: [],
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
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<boolean> {
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookUrl = this.getNodeWebhookUrl('default');
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
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'],
inputs: [],
outputs: [NodeConnectionTypes.Main],
polling: true,
webhooks: [{ name: 'default', httpMethod: '...', path: '...', responseMode: '...' }],
eventTriggerDescription: 'Waiting for events from MyService',
activationMessage: 'Your trigger is now active and listening.',
};
Reference Files
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/