Skip to main content
Glama
form-handler.tsβ€’3.5 kB
/** * Form trigger handler * * Handles form-based workflow triggers: * - POST to /form/<workflowId> or /form-test/<workflowId> * - Passes form fields as request body * - Workflow must be active (for production endpoint) */ import { z } from 'zod'; import axios, { AxiosRequestConfig } from 'axios'; import { Workflow, WebhookRequest } from '../../types/n8n-api'; import { TriggerType, TriggerResponse, TriggerHandlerCapabilities, DetectedTrigger, FormTriggerInput, } from '../types'; import { BaseTriggerHandler } from './base-handler'; /** * Zod schema for form input validation */ const formInputSchema = z.object({ workflowId: z.string(), triggerType: z.literal('form'), formData: z.record(z.unknown()).optional(), data: z.record(z.unknown()).optional(), headers: z.record(z.string()).optional(), timeout: z.number().optional(), waitForResponse: z.boolean().optional(), }); /** * Form trigger handler */ export class FormHandler extends BaseTriggerHandler<FormTriggerInput> { readonly triggerType: TriggerType = 'form'; readonly capabilities: TriggerHandlerCapabilities = { requiresActiveWorkflow: true, canPassInputData: true, }; readonly inputSchema = formInputSchema; async execute( input: FormTriggerInput, workflow: Workflow, triggerInfo?: DetectedTrigger ): Promise<TriggerResponse> { const startTime = Date.now(); try { // Build form URL const baseUrl = this.getBaseUrl(); if (!baseUrl) { return this.errorResponse(input, 'Cannot determine n8n base URL', startTime); } // Form triggers use /form/<path> endpoint // The path can be from trigger info or workflow ID const formPath = triggerInfo?.node?.parameters?.path || input.workflowId; const formUrl = `${baseUrl.replace(/\/+$/, '')}/form/${formPath}`; // Merge formData and data (formData takes precedence) const formFields = { ...input.data, ...input.formData, }; // SSRF protection const { SSRFProtection } = await import('../../utils/ssrf-protection'); const validation = await SSRFProtection.validateWebhookUrl(formUrl); if (!validation.valid) { return this.errorResponse(input, `SSRF protection: ${validation.reason}`, startTime); } // Build request config const config: AxiosRequestConfig = { method: 'POST', url: formUrl, headers: { 'Content-Type': 'application/json', ...input.headers, }, data: formFields, timeout: input.timeout || (input.waitForResponse !== false ? 120000 : 30000), validateStatus: (status) => status < 500, }; // Make the request const response = await axios.request(config); return this.normalizeResponse(response.data, input, startTime, { status: response.status, statusText: response.statusText, metadata: { duration: Date.now() - startTime, }, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; // Try to extract execution ID from error if available const errorDetails = (error as any)?.response?.data; const executionId = errorDetails?.executionId || errorDetails?.id; return this.errorResponse(input, errorMessage, startTime, { executionId, code: (error as any)?.code, details: errorDetails, }); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/czlonkowski/n8n-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server