diff --git a/packages/nodes-base/credentials/AdaloApi.credentials.ts b/packages/nodes-base/credentials/AdaloApi.credentials.ts
new file mode 100644
index 0000000000..bbde6e2446
--- /dev/null
+++ b/packages/nodes-base/credentials/AdaloApi.credentials.ts
@@ -0,0 +1,34 @@
+import { IAuthenticateGeneric, ICredentialType, INodeProperties } from 'n8n-workflow';
+
+export class AdaloApi implements ICredentialType {
+ name = 'adaloApi';
+ displayName = 'Adalo API';
+ documentationUrl = 'adalo';
+ properties: INodeProperties[] = [
+ {
+ displayName: 'API Key',
+ name: 'apiKey',
+ type: 'string',
+ default: '',
+ description:
+ 'The Adalo API is available on paid Adalo plans, find more information here',
+ },
+ {
+ displayName: 'App ID',
+ name: 'appId',
+ type: 'string',
+ default: '',
+ description:
+ 'You can get App ID from the URL of your app. For example, if your app URL is https://app.adalo.com/apps/1234567890/screens, then your App ID is 1234567890.',
+ },
+ ];
+
+ authenticate: IAuthenticateGeneric = {
+ type: 'generic',
+ properties: {
+ headers: {
+ Authorization: '=Bearer {{$credentials.apiKey}}',
+ },
+ },
+ };
+}
diff --git a/packages/nodes-base/nodes/Adalo/Adalo.node.json b/packages/nodes-base/nodes/Adalo/Adalo.node.json
new file mode 100644
index 0000000000..10b6ee6ffd
--- /dev/null
+++ b/packages/nodes-base/nodes/Adalo/Adalo.node.json
@@ -0,0 +1,20 @@
+{
+ "node": "n8n-nodes-base.adalo",
+ "nodeVersion": "1.0",
+ "codexVersion": "1.0",
+ "categories": [
+ "Data & Storage"
+ ],
+ "resources": {
+ "credentialDocumentation": [
+ {
+ "url": "https://docs.n8n.io/credentials/adalo"
+ }
+ ],
+ "primaryDocumentation": [
+ {
+ "url": "https://docs.n8n.io/nodes/n8n-nodes-base.adalo/"
+ }
+ ]
+ }
+}
diff --git a/packages/nodes-base/nodes/Adalo/Adalo.node.ts b/packages/nodes-base/nodes/Adalo/Adalo.node.ts
new file mode 100644
index 0000000000..3f35023bbe
--- /dev/null
+++ b/packages/nodes-base/nodes/Adalo/Adalo.node.ts
@@ -0,0 +1,212 @@
+import {
+ IDataObject,
+ IExecuteSingleFunctions,
+ IHttpRequestOptions,
+ IN8nHttpFullResponse,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+import { collectionFields } from './CollectionDescription';
+import { FieldsUiValues } from './types';
+
+export class Adalo implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Adalo',
+ name: 'adalo',
+ icon: 'file:adalo.svg',
+ group: ['transform'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["collectionId"]}}',
+ description: 'Consume Adalo API',
+ defaults: {
+ name: 'Adalo',
+ color: '#4f44d7',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'adaloApi',
+ required: true,
+ },
+ ],
+ requestDefaults: {
+ baseURL: '=https://api.adalo.com/v0/apps/{{$credentials.appId}}',
+ },
+ requestOperations: {
+ pagination: {
+ type: 'offset',
+ properties: {
+ limitParameter: 'limit',
+ offsetParameter: 'offset',
+ pageSize: 100,
+ type: 'query',
+ },
+ },
+ },
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ noDataExpression: true,
+ default: 'collection',
+ options: [
+ {
+ name: 'Collection',
+ value: 'collection',
+ },
+ ],
+ },
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create a row',
+ routing: {
+ send: {
+ preSend: [this.presendCreateUpdate],
+ },
+ request: {
+ method: 'POST',
+ url: '=/collections/{{$parameter["collectionId"]}}',
+ },
+ },
+ action: 'Create a row',
+ },
+ {
+ name: 'Delete',
+ value: 'delete',
+ description: 'Delete a row',
+ routing: {
+ request: {
+ method: 'DELETE',
+ url: '=/collections/{{$parameter["collectionId"]}}/{{$parameter["rowId"]}}',
+ },
+ output: {
+ postReceive: [
+ {
+ type: 'set',
+ properties: {
+ value: '={{ { "success": true } }}',
+ },
+ },
+ ],
+ },
+ },
+ action: 'Delete a row',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Retrieve a row',
+ routing: {
+ request: {
+ method: 'GET',
+ url: '=/collections/{{$parameter["collectionId"]}}/{{$parameter["rowId"]}}',
+ },
+ },
+ action: 'Retrieve a row',
+ },
+ {
+ name: 'Get Many',
+ value: 'getAll',
+ description: 'Retrieve many rows',
+ routing: {
+ request: {
+ method: 'GET',
+ url: '=/collections/{{$parameter["collectionId"]}}',
+ qs: {
+ limit: '={{$parameter["limit"]}}',
+ },
+ },
+ send: {
+ paginate: '={{$parameter["returnAll"]}}',
+ },
+ output: {
+ postReceive: [
+ {
+ type: 'rootProperty',
+ properties: {
+ property: 'records',
+ },
+ },
+ ],
+ },
+ },
+ action: 'Retrieve all rows',
+ },
+ {
+ name: 'Update',
+ value: 'update',
+ description: 'Update a row',
+ routing: {
+ send: {
+ preSend: [this.presendCreateUpdate],
+ },
+ request: {
+ method: 'PUT',
+ url: '=/collections/{{$parameter["collectionId"]}}/{{$parameter["rowId"]}}',
+ },
+ },
+ action: 'Update a row',
+ },
+ ],
+ default: 'getAll',
+ },
+ {
+ displayName: 'Collection ID',
+ name: 'collectionId',
+ type: 'string',
+ required: true,
+ default: '',
+ description:
+ 'Open your Adalo application and click on the three buttons beside the collection name, then select API Documentation',
+ hint: "You can find information about app's collections on https://app.adalo.com/apps/your-app-id/api-docs",
+ displayOptions: {
+ show: {
+ resource: ['collection'],
+ },
+ },
+ },
+ ...collectionFields,
+ ],
+ };
+
+ async presendCreateUpdate(
+ this: IExecuteSingleFunctions,
+ requestOptions: IHttpRequestOptions,
+ ): Promise {
+ const dataToSend = this.getNodeParameter('dataToSend', 0) as 'defineBelow' | 'autoMapInputData';
+
+ requestOptions.body = {};
+
+ if (dataToSend === 'autoMapInputData') {
+ const inputData = this.getInputData();
+ const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore') as string;
+
+ const inputKeysToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
+ const inputKeys = Object.keys(inputData.json).filter(
+ (key) => !inputKeysToIgnore.includes(key),
+ );
+
+ for (const key of inputKeys) {
+ (requestOptions.body as IDataObject)[key] = inputData.json[key];
+ }
+ } else {
+ const fields = this.getNodeParameter('fieldsUi.fieldValues') as FieldsUiValues;
+
+ for (const field of fields) {
+ (requestOptions.body as IDataObject)[field.fieldId] = field.fieldValue;
+ }
+ }
+
+ return requestOptions;
+ }
+}
diff --git a/packages/nodes-base/nodes/Adalo/CollectionDescription.ts b/packages/nodes-base/nodes/Adalo/CollectionDescription.ts
new file mode 100644
index 0000000000..20c85f2e86
--- /dev/null
+++ b/packages/nodes-base/nodes/Adalo/CollectionDescription.ts
@@ -0,0 +1,139 @@
+import { INodeProperties } from 'n8n-workflow';
+
+export const collectionFields: INodeProperties[] = [
+ {
+ displayName: 'Row ID',
+ name: 'rowId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: ['get', 'delete', 'update'],
+ resource: ['collection'],
+ },
+ },
+ default: '',
+ required: true,
+ },
+
+ /**
+ * create / update
+ */
+
+ {
+ displayName: 'Data to Send',
+ name: 'dataToSend',
+ type: 'options',
+ options: [
+ {
+ name: 'Auto-Map Input Data to Columns',
+ value: 'autoMapInputData',
+ description: 'Use when node input properties match destination column names',
+ },
+ {
+ name: 'Define Below for Each Column',
+ value: 'defineBelow',
+ description: 'Set the value for each destination column',
+ },
+ ],
+ displayOptions: {
+ show: {
+ operation: ['create', 'update'],
+ resource: ['collection'],
+ },
+ },
+ default: 'defineBelow',
+ description: 'Whether to insert the input data this node receives in the new row',
+ },
+ {
+ displayName: 'Inputs to Ignore',
+ name: 'inputsToIgnore',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: ['create', 'update'],
+ dataToSend: ['autoMapInputData'],
+ resource: ['collection'],
+ },
+ },
+ default: '',
+ description:
+ 'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
+ placeholder: 'Enter properties...',
+ },
+ {
+ displayName: 'Fields to Send',
+ name: 'fieldsUi',
+ placeholder: 'Add Field',
+ type: 'fixedCollection',
+ description:
+ 'Field must be defined in the collection, otherwise it will be ignored. If field defined in the collection is not set here, it will be set to null.',
+ typeOptions: {
+ multipleValueButtonText: 'Add Field to Send',
+ multipleValues: true,
+ },
+ displayOptions: {
+ show: {
+ operation: ['create', 'update'],
+ dataToSend: ['defineBelow'],
+ resource: ['collection'],
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Field',
+ name: 'fieldValues',
+ values: [
+ {
+ displayName: 'Field ID',
+ name: 'fieldId',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Field Value',
+ name: 'fieldValue',
+ type: 'string',
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+
+ /**
+ * getAll
+ */
+
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ default: false,
+ description: 'Whether to return all results or only up to a given limit',
+ displayOptions: {
+ show: {
+ operation: ['getAll'],
+ resource: ['collection'],
+ },
+ },
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ default: 100,
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ displayOptions: {
+ show: {
+ operation: ['getAll'],
+ resource: ['collection'],
+ returnAll: [false],
+ },
+ },
+ description: 'Max number of results to return',
+ },
+];
diff --git a/packages/nodes-base/nodes/Adalo/adalo.svg b/packages/nodes-base/nodes/Adalo/adalo.svg
new file mode 100644
index 0000000000..2f1ff40fcd
--- /dev/null
+++ b/packages/nodes-base/nodes/Adalo/adalo.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/packages/nodes-base/nodes/Adalo/types.d.ts b/packages/nodes-base/nodes/Adalo/types.d.ts
new file mode 100644
index 0000000000..d23cc3791b
--- /dev/null
+++ b/packages/nodes-base/nodes/Adalo/types.d.ts
@@ -0,0 +1,11 @@
+export type AdaloCredentials = {
+ apiKey: string;
+ appId: string;
+};
+
+export type FieldsUiValues = Array<{
+ fieldId: string;
+ fieldValue: string;
+}>;
+
+export type Operation = 'create' | 'delete' | 'update' | 'get' | 'getAll';
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index f3c9f0d9bd..be33929eba 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -33,6 +33,7 @@
"dist/credentials/ActiveCampaignApi.credentials.js",
"dist/credentials/AcuitySchedulingApi.credentials.js",
"dist/credentials/AcuitySchedulingOAuth2Api.credentials.js",
+ "dist/credentials/AdaloApi.credentials.js",
"dist/credentials/AffinityApi.credentials.js",
"dist/credentials/AgileCrmApi.credentials.js",
"dist/credentials/AirtableApi.credentials.js",
@@ -338,6 +339,7 @@
"dist/nodes/ActiveCampaign/ActiveCampaign.node.js",
"dist/nodes/ActiveCampaign/ActiveCampaignTrigger.node.js",
"dist/nodes/AcuityScheduling/AcuitySchedulingTrigger.node.js",
+ "dist/nodes/Adalo/Adalo.node.js",
"dist/nodes/Affinity/Affinity.node.js",
"dist/nodes/Affinity/AffinityTrigger.node.js",
"dist/nodes/AgileCrm/AgileCrm.node.js",