diff --git a/packages/cli/templates/form-trigger.handlebars b/packages/cli/templates/form-trigger.handlebars
index 6bad1a02d8..21e10d86a9 100644
--- a/packages/cli/templates/form-trigger.handlebars
+++ b/packages/cli/templates/form-trigger.handlebars
@@ -377,6 +377,10 @@
{{/if}}
+ {{#if isHidden}}
+
+ {{/if}}
+
{{#if isTextarea}}
diff --git a/packages/nodes-base/nodes/Form/Form.node.ts b/packages/nodes-base/nodes/Form/Form.node.ts
index 735cbc4065..03f27219fe 100644
--- a/packages/nodes-base/nodes/Form/Form.node.ts
+++ b/packages/nodes-base/nodes/Form/Form.node.ts
@@ -266,7 +266,15 @@ export class Form extends Node {
});
}
} else {
- fields = context.getNodeParameter('formFields.values', []) as FormFieldsParameter;
+ fields = (context.getNodeParameter('formFields.values', []) as FormFieldsParameter).map(
+ (field) => {
+ if (field.fieldType === 'hiddenField') {
+ field.fieldLabel = field.fieldName as string;
+ }
+
+ return field;
+ },
+ );
}
const method = context.getRequestObject().method;
diff --git a/packages/nodes-base/nodes/Form/common.descriptions.ts b/packages/nodes-base/nodes/Form/common.descriptions.ts
index 67cf3515d1..9a3fab2e0f 100644
--- a/packages/nodes-base/nodes/Form/common.descriptions.ts
+++ b/packages/nodes-base/nodes/Form/common.descriptions.ts
@@ -64,6 +64,11 @@ export const formFields: INodeProperties = {
placeholder: 'e.g. What is your name?',
description: 'Label that appears above the input field',
required: true,
+ displayOptions: {
+ hide: {
+ fieldType: ['hiddenField'],
+ },
+ },
},
{
displayName: 'Element Type',
@@ -92,6 +97,10 @@ export const formFields: INodeProperties = {
name: 'File',
value: 'file',
},
+ {
+ name: 'Hidden Field',
+ value: 'hiddenField',
+ },
{
name: 'Number',
value: 'number',
@@ -119,7 +128,33 @@ export const formFields: INodeProperties = {
default: '',
displayOptions: {
hide: {
- fieldType: ['dropdown', 'date', 'file', 'html'],
+ fieldType: ['dropdown', 'date', 'file', 'html', 'hiddenField'],
+ },
+ },
+ },
+ {
+ displayName: 'Field Name',
+ name: 'fieldName',
+ description:
+ 'The name of the field, used in input attributes and referenced by the workflow',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ fieldType: ['hiddenField'],
+ },
+ },
+ },
+ {
+ displayName: 'Field Value',
+ name: 'fieldValue',
+ description:
+ 'Input value can be set here or will be passed as a query parameter via Field Name if no value is set',
+ type: 'string',
+ default: '',
+ displayOptions: {
+ show: {
+ fieldType: ['hiddenField'],
},
},
},
@@ -242,7 +277,7 @@ export const formFields: INodeProperties = {
'Whether to require the user to enter a value for this field before submitting the form',
displayOptions: {
hide: {
- fieldType: ['html'],
+ fieldType: ['html', 'hiddenField'],
},
},
},
diff --git a/packages/nodes-base/nodes/Form/test/Form.node.test.ts b/packages/nodes-base/nodes/Form/test/Form.node.test.ts
index 9dd0e4bbf2..537804dea3 100644
--- a/packages/nodes-base/nodes/Form/test/Form.node.test.ts
+++ b/packages/nodes-base/nodes/Form/test/Form.node.test.ts
@@ -106,7 +106,16 @@ describe('Form Node', () => {
mockWebhookFunctions.getNodeParameter.mockImplementation((paramName: string) => {
if (paramName === 'operation') return 'page';
if (paramName === 'useJson') return false;
- if (paramName === 'formFields.values') return [{ fieldLabel: 'test' }];
+ if (paramName === 'formFields.values')
+ return [
+ { fieldLabel: 'test' },
+ {
+ fieldName: 'Powerpuff Girl',
+ fieldValue: 'Blossom',
+ fieldType: 'hiddenField',
+ fieldLabel: '',
+ },
+ ];
if (paramName === 'options') {
return {
formTitle: 'Form Title',
@@ -121,7 +130,42 @@ describe('Form Node', () => {
await form.webhook(mockWebhookFunctions);
- expect(mockResponseObject.render).toHaveBeenCalledWith('form-trigger', expect.any(Object));
+ expect(mockResponseObject.render).toHaveBeenCalledWith('form-trigger', {
+ appendAttribution: 'test',
+ buttonLabel: 'Form Button',
+ formDescription: 'Form Description',
+ formDescriptionMetadata: 'Form Description',
+ formFields: [
+ {
+ id: 'field-0',
+ errorId: 'error-field-0',
+ label: 'test',
+ inputRequired: '',
+ defaultValue: '',
+ isInput: true,
+ placeholder: undefined,
+ type: undefined,
+ },
+ {
+ id: 'field-1',
+ errorId: 'error-field-1',
+ label: 'Powerpuff Girl',
+ inputRequired: '',
+ defaultValue: '',
+ placeholder: undefined,
+ hiddenName: 'Powerpuff Girl',
+ hiddenValue: 'Blossom',
+ isHidden: true,
+ },
+ ],
+ formSubmittedText: 'Your response has been recorded',
+ formTitle: 'Form Title',
+ n8nWebsiteLink: 'https://n8n.io/?utm_source=n8n-internal&utm_medium=form-trigger',
+ testRun: true,
+ useResponseData: false,
+ validForm: true,
+ formSubmittedHeader: undefined,
+ });
});
it('should return form data for POST request', async () => {
@@ -182,6 +226,7 @@ describe('Form Node', () => {
if (paramName === 'completionTitle') return 'Test Title';
if (paramName === 'completionMessage') return 'Test Message';
if (paramName === 'redirectUrl') return '';
+ if (paramName === 'formFields.values') return [];
return {};
});
mockWebhookFunctions.getParentNodes.mockReturnValue([
@@ -225,6 +270,8 @@ describe('Form Node', () => {
if (paramName === 'completionTitle') return 'Test Title';
if (paramName === 'completionMessage') return 'Test Message';
if (paramName === 'redirectUrl') return 'https://n8n.io';
+ if (paramName === 'formFields.values') return [];
+
return {};
});
mockWebhookFunctions.getParentNodes.mockReturnValue([
diff --git a/packages/nodes-base/nodes/Form/test/utils.test.ts b/packages/nodes-base/nodes/Form/test/utils.test.ts
index 4d1bf39c36..a6130cfb3c 100644
--- a/packages/nodes-base/nodes/Form/test/utils.test.ts
+++ b/packages/nodes-base/nodes/Form/test/utils.test.ts
@@ -110,6 +110,12 @@ describe('FormTrigger, formWebhook', () => {
html: '
Test HTML
',
requiredField: false,
},
+ {
+ fieldName: 'Powerpuff Girl',
+ fieldValue: 'Blossom',
+ fieldType: 'hiddenField',
+ fieldLabel: '',
+ },
];
executeFunctions.getNodeParameter.calledWith('formFields.values').mockReturnValue(formFields);
@@ -174,6 +180,17 @@ describe('FormTrigger, formWebhook', () => {
html: '
Test HTML
',
isHtml: true,
},
+ {
+ id: 'field-5',
+ errorId: 'error-field-5',
+ hiddenName: 'Powerpuff Girl',
+ hiddenValue: 'Blossom',
+ label: 'Powerpuff Girl',
+ isHidden: true,
+ inputRequired: '',
+ defaultValue: '',
+ placeholder: undefined,
+ },
],
formSubmittedText: 'Your response has been recorded',
formTitle: 'Test Form',
@@ -300,9 +317,21 @@ describe('FormTrigger, prepareFormData', () => {
acceptFileTypes: '.jpg,.png',
multipleFiles: true,
},
+ {
+ fieldLabel: 'username',
+ fieldName: 'username',
+ fieldValue: 'powerpuffgirl125',
+ fieldType: 'hiddenField',
+ },
+ {
+ fieldLabel: 'villain',
+ fieldName: 'villain',
+ fieldValue: 'Mojo Dojo',
+ fieldType: 'hiddenField',
+ },
];
- const query = { Name: 'John Doe', Email: 'john@example.com' };
+ const query = { Name: 'John Doe', Email: 'john@example.com', villain: 'princess morbucks' };
const result = prepareFormData({
formTitle: 'Test Form',
@@ -368,6 +397,28 @@ describe('FormTrigger, prepareFormData', () => {
acceptFileTypes: '.jpg,.png',
multipleFiles: 'multiple',
},
+ {
+ id: 'field-4',
+ errorId: 'error-field-4',
+ label: 'username',
+ inputRequired: '',
+ defaultValue: '',
+ placeholder: undefined,
+ hiddenName: 'username',
+ hiddenValue: 'powerpuffgirl125',
+ isHidden: true,
+ },
+ {
+ id: 'field-5',
+ errorId: 'error-field-5',
+ label: 'villain',
+ inputRequired: '',
+ defaultValue: 'princess morbucks',
+ placeholder: undefined,
+ hiddenName: 'villain',
+ isHidden: true,
+ hiddenValue: 'princess morbucks',
+ },
],
useResponseData: true,
appendAttribution: true,
diff --git a/packages/nodes-base/nodes/Form/utils.ts b/packages/nodes-base/nodes/Form/utils.ts
index 2510be56ac..ff10c69d08 100644
--- a/packages/nodes-base/nodes/Form/utils.ts
+++ b/packages/nodes-base/nodes/Form/utils.ts
@@ -167,6 +167,11 @@ export function prepareFormData({
} else if (fieldType === 'html') {
input.isHtml = true;
input.html = field.html as string;
+ } else if (fieldType === 'hiddenField') {
+ input.isHidden = true;
+ input.hiddenName = field.fieldName as string;
+ input.hiddenValue =
+ input.defaultValue === '' ? (field.fieldValue as string) : input.defaultValue;
} else {
input.isInput = true;
input.type = fieldType as 'text' | 'number' | 'date' | 'email';
@@ -432,6 +437,9 @@ export async function formWebhook(
if (field.fieldType === 'html') {
field.html = sanitizeHtml(field.html as string);
}
+ if (field.fieldType === 'hiddenField') {
+ field.fieldLabel = field.fieldName as string;
+ }
return field;
},
);
diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts
index ff0b1ad973..0d67b07f37 100644
--- a/packages/workflow/src/Interfaces.ts
+++ b/packages/workflow/src/Interfaces.ts
@@ -2693,6 +2693,8 @@ export type FormFieldsParameter = Array<{
formatDate?: string;
html?: string;
placeholder?: string;
+ fieldName?: string;
+ fieldValue?: string;
}>;
export type FieldTypeMap = {