/** * @node form-trigger * @name Form * @category triggers * @version 1.0.0 * @description Publishes an HTML form at a shareable URL. Submitting it starts the workflow. * @trigger * @icon clipboard-list */ const configSchema = { type: 'object', properties: { title: { type: 'string', title: 'Form Title', description: 'Shown as the heading of the page', default: 'Untitled form' }, description: { type: 'string', title: 'Description', description: 'Shown under the heading. Plain text - it is escaped, not rendered as HTML.', default: '' }, fields: { type: 'array', title: 'Fields', description: 'The questions on the form, in order.', default: [], items: { type: 'object', properties: { name: { type: 'string', title: 'Name', description: 'The key the workflow reads, e.g. "image" for $node[\'Form\'].form.image. Letters, digits and underscore, not starting with a digit.' }, label: { type: 'string', title: 'Label', description: 'Shown next to the input' }, type: { type: 'string', title: 'Type', enum: ['text', 'textarea', 'number', 'select', 'checkbox', 'date', 'file'], default: 'text' }, required: { type: 'boolean', title: 'Required', default: false }, placeholder: { type: 'string', title: 'Placeholder', default: '' }, options: { type: 'array', title: 'Options', description: 'Choices, for a Select field', items: { type: 'string' }, default: [] }, accept: { type: 'string', title: 'Accept', description: 'For a File field, e.g. "image/*". A browser hint only - it is not enforced.', default: '' }, maxSizeMb: { type: 'number', title: 'Max Size (MB)', description: 'For a File field. 0 uses the server limit.', default: 0 } } } }, password: { type: 'string', title: 'Password', format: 'password', description: 'Leave empty for anyone with the link. This is stored readable in the workflow, so treat it as a gate against a forwarded link, not as a secret.', default: '' }, responseMode: { type: 'string', title: 'After Submit', enum: ['immediate', 'wait'], description: 'Immediate returns a thank-you page at once and lets the run continue - needed for anything taking more than about 30 seconds. Wait holds the browser until the workflow finishes, so a Respond to Webhook node can answer.', default: 'immediate' }, responseMessage: { type: 'string', title: 'Thank-you Message', default: 'Thanks, your answer was received.', 'x-if': 'responseMode' }, path: { type: 'string', title: 'Path Filter', description: 'Optional path suffix. Leave empty to answer at the bare form URL.', default: '' } } }; const inputSchema = { type: 'object', properties: {} }; const outputSchema = { type: 'object', properties: { form: { type: 'object', description: 'The answers, keyed by field name. A file field holds a binary object.' }, submittedAt: { type: 'number', description: 'Milliseconds since the epoch' }, clientIp: { type: 'string' } } }; async function execute(config, input, context) { // Every value here is built by the webhook controller, which has already // validated it. Executed directly - from the editor's Run button - there is // no submission, and an empty form is the honest answer rather than an error. // The runner hands the trigger node's payload to `input`, not `context` - // `context` only carries executionId/nodeId/workflowId. const triggerData = input || {}; return { form: triggerData.form || {}, submittedAt: triggerData.submittedAt || Date.now(), clientIp: triggerData.clientIp || '' }; } module.exports = { configSchema, inputSchema, outputSchema, execute };