diff --git a/packages/nodes-base/credentials/FormIoApi.credentials.ts b/packages/nodes-base/credentials/FormIoApi.credentials.ts
new file mode 100644
index 0000000000..50439cd738
--- /dev/null
+++ b/packages/nodes-base/credentials/FormIoApi.credentials.ts
@@ -0,0 +1,57 @@
+import {
+ ICredentialType,
+ INodeProperties,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class FormIoApi implements ICredentialType {
+ name = 'formIoApi';
+ displayName = 'Form.io API';
+ properties: INodeProperties[] = [
+ {
+ displayName: 'Environment',
+ name: 'environment',
+ type: 'options',
+ default: 'cloudHosted',
+ options: [
+ {
+ name: 'Cloud-hosted',
+ value: 'cloudHosted',
+ },
+ {
+ name: 'Self-hosted',
+ value: 'selfHosted',
+ },
+ ],
+ },
+ {
+ displayName: 'Self-hosted domain',
+ name: 'domain',
+ type: 'string',
+ default: '',
+ placeholder: 'https://www.mydomain.com',
+ displayOptions: {
+ show: {
+ environment: [
+ 'selfHosted',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Email',
+ name: 'email',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'Password',
+ name: 'password',
+ type: 'string' as NodePropertyTypes,
+ typeOptions: {
+ password: true,
+ },
+ default: '',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/FormIo/FormIoTrigger.node.ts b/packages/nodes-base/nodes/FormIo/FormIoTrigger.node.ts
new file mode 100644
index 0000000000..30cc69e3b8
--- /dev/null
+++ b/packages/nodes-base/nodes/FormIo/FormIoTrigger.node.ts
@@ -0,0 +1,205 @@
+import {
+ IHookFunctions,
+ IWebhookFunctions,
+} from 'n8n-core';
+
+import {
+ ILoadOptionsFunctions,
+ INodePropertyOptions,
+ INodeType,
+ INodeTypeDescription,
+ IWebhookResponseData,
+} from 'n8n-workflow';
+
+import {
+ formIoApiRequest,
+} from './GenericFunctions';
+
+export class FormIoTrigger implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Form.io Trigger',
+ name: 'formIoTrigger',
+ icon: 'file:formio.svg',
+ group: ['trigger'],
+ version: 1,
+ subtitle: '={{$parameter["event"]}}',
+ description: 'Handle form.io events via webhooks',
+ defaults: {
+ name: 'Form.io Trigger',
+ color: '#6ad7b9',
+ },
+ inputs: [],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'formIoApi',
+ required: true,
+ },
+ ],
+ webhooks: [
+ {
+ name: 'default',
+ httpMethod: 'POST',
+ responseMode: 'onReceived',
+ path: 'webhook',
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Project Name/ID',
+ name: 'projectId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsMethod: 'getProjects',
+ },
+ required: true,
+ default: '',
+ description: `Choose from the list or specify an ID. You can also specify the ID using an expression`
+ },
+ {
+ displayName: 'Form Name/ID',
+ name: 'formId',
+ type: 'options',
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'projectId',
+ ],
+ loadOptionsMethod: 'getForms',
+ },
+ required: true,
+ default: '',
+ description: `Choose from the list or specify an ID. You can also specify the ID using an expression`
+ },
+ {
+ displayName: 'Trigger Events',
+ name: 'events',
+ type: 'multiOptions',
+ options: [
+ {
+ name: 'Submission Created',
+ value: 'create',
+ },
+ {
+ name: 'Submission Updated',
+ value: 'update',
+ },
+ ],
+ required: true,
+ default: '',
+ },
+ {
+ displayName: 'Simplify Response',
+ name: 'simple',
+ type: 'boolean',
+ default: true,
+ description: 'Return a simplified version of the response instead of the raw data',
+ },
+ ],
+ };
+
+ methods = {
+ loadOptions: {
+ async getProjects(this: ILoadOptionsFunctions): Promise {
+ const projects = await formIoApiRequest.call(this, 'GET', '/project', {});
+ const returnData: INodePropertyOptions[] = [];
+ for (const project of projects) {
+ returnData.push({
+ name: project.title,
+ value: project._id,
+ });
+ }
+ return returnData;
+ },
+ async getForms(this: ILoadOptionsFunctions): Promise {
+ const projectId = this.getCurrentNodeParameter('projectId') as string;
+ const forms = await formIoApiRequest.call(this, 'GET', `/project/${projectId}/form`, {});
+ const returnData: INodePropertyOptions[] = [];
+ for (const form of forms) {
+ returnData.push({
+ name: form.title,
+ value: form._id,
+ });
+ }
+ return returnData;
+ },
+ },
+ };
+
+ // @ts-ignore
+ webhookMethods = {
+ default: {
+ async checkExists(this: IHookFunctions): Promise {
+ const webhookData = this.getWorkflowStaticData('node');
+ const webhookUrl = this.getNodeWebhookUrl('default');
+ const formId = this.getNodeParameter('formId') as string;
+ const projectId = this.getNodeParameter('projectId') as string;
+ const method = this.getNodeParameter('events') as string[];
+ const actions = await formIoApiRequest.call(this, 'GET', `/project/${projectId}/form/${formId}/action`);
+ for (const action of actions) {
+ if (action.name === 'webhook') {
+ if (action.settings.url === webhookUrl &&
+ // tslint:disable-next-line: no-any
+ (action.method.length === method.length && action.method.every((value: any) => method.includes(value)))) {
+ webhookData.webhookId = action._id;
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+
+ async create(this: IHookFunctions): Promise {
+ const webhookData = this.getWorkflowStaticData('node');
+ const formId = this.getNodeParameter('formId') as string;
+ const projectId = this.getNodeParameter('projectId') as string;
+ const webhookUrl = this.getNodeWebhookUrl('default') as string;
+ const method = this.getNodeParameter('events') as string[];
+ const payload = {
+ data: {
+ name: `webhook`,
+ title: `webhook-n8n:${webhookUrl}`,
+ method,
+ handler: [
+ 'after',
+ ],
+ priority: 0,
+ settings: {
+ method: 'post',
+ block: false,
+ url: webhookUrl,
+ },
+ condition: {
+ field: 'submit',
+ },
+ },
+ };
+ const webhook = await formIoApiRequest.call(this, 'POST', `/project/${projectId}/form/${formId}/action`, payload);
+ webhookData.webhookId = webhook._id;
+ return true;
+ },
+
+ async delete(this: IHookFunctions): Promise {
+ const webhookData = this.getWorkflowStaticData('node');
+ const formId = this.getNodeParameter('formId') as string;
+ const projectId = this.getNodeParameter('projectId') as string;
+ await formIoApiRequest.call(this, 'DELETE', `/project/${projectId}/form/${formId}/action/${webhookData.webhookId}`);
+ delete webhookData.webhookId;
+ return true;
+ },
+ },
+ };
+
+ async webhook(this: IWebhookFunctions): Promise {
+ const req = this.getRequestObject();
+ const simple = this.getNodeParameter('simple') as boolean;
+ let response = req.body.request;
+ if (simple === true) {
+ response = response.data;
+ }
+ return {
+ workflowData: [
+ this.helpers.returnJsonArray(response),
+ ],
+ };
+ }
+}
diff --git a/packages/nodes-base/nodes/FormIo/GenericFunctions.ts b/packages/nodes-base/nodes/FormIo/GenericFunctions.ts
new file mode 100644
index 0000000000..ae42a4f28f
--- /dev/null
+++ b/packages/nodes-base/nodes/FormIo/GenericFunctions.ts
@@ -0,0 +1,84 @@
+import {
+ IExecuteFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ IHookFunctions,
+ IWebhookFunctions,
+ NodeApiError,
+} from 'n8n-workflow';
+
+
+interface IFormIoCredentials {
+ environment: 'cloudHosted' | ' selfHosted';
+ domain?: string;
+ email: string,
+ password: string,
+}
+
+/**
+ * Method has the logic to get jwt token from Form.io
+ * @param this
+ */
+async function getToken(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, credentials: IFormIoCredentials) {
+ const base = credentials.domain || 'https://formio.form.io';
+ const options = {
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ method: 'POST',
+ body: {
+ data: {
+ email: credentials.email,
+ password: credentials.password,
+ },
+ },
+ uri: `${base}/user/login`,
+ json: true,
+ resolveWithFullResponse: true,
+ };
+
+ console.log('options');
+ console.log(JSON.stringify(options, null, 2));
+
+ try {
+ const responseObject = await this.helpers.request!(options);
+ return responseObject.headers['x-jwt-token'];
+ } catch (error) {
+ throw new Error(`Authentication Failed for Form.io. Please provide valid credentails/ endpoint details`);
+ }
+}
+
+/**
+ * Method will call register or list webhooks based on the passed method in the parameter
+ * @param this
+ * @param method
+ */
+export async function formIoApiRequest(this: IHookFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, endpoint: string, body = {}, qs = {}): Promise { // tslint:disable-line:no-any
+
+ const credentials = await this.getCredentials('formIoApi') as unknown as IFormIoCredentials;
+
+ const token = await getToken.call(this, credentials);
+
+ const base = credentials.domain || 'https://api.form.io';
+
+ const options = {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-jwt-token': token,
+ },
+ method,
+ body,
+ qs,
+ uri: `${base}${endpoint}`,
+ json: true,
+ };
+
+ try {
+ return await this.helpers.request!.call(this, options);
+ } catch (error) {
+ throw new NodeApiError(this.getNode(), error);
+ }
+}
diff --git a/packages/nodes-base/nodes/FormIo/formio.svg b/packages/nodes-base/nodes/FormIo/formio.svg
new file mode 100644
index 0000000000..0887bca61d
--- /dev/null
+++ b/packages/nodes-base/nodes/FormIo/formio.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index de42d71c59..67c7c6f966 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -90,6 +90,7 @@
"dist/credentials/FileMaker.credentials.js",
"dist/credentials/FlowApi.credentials.js",
"dist/credentials/Ftp.credentials.js",
+ "dist/credentials/FormIoApi.credentials.js",
"dist/credentials/GetResponseApi.credentials.js",
"dist/credentials/GetResponseOAuth2Api.credentials.js",
"dist/credentials/GhostAdminApi.credentials.js",
@@ -382,6 +383,7 @@
"dist/nodes/Ftp.node.js",
"dist/nodes/Freshdesk/Freshdesk.node.js",
"dist/nodes/FreshworksCrm/FreshworksCrm.node.js",
+ "dist/nodes/FormIo/FormIoTrigger.node.js",
"dist/nodes/Flow/Flow.node.js",
"dist/nodes/Flow/FlowTrigger.node.js",
"dist/nodes/Function.node.js",