first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,146 @@
import type {
IBinaryKeyData,
IDataObject,
IExecuteFunctions,
IHookFunctions,
IHttpRequestMethods,
ILoadOptionsFunctions,
INodeExecutionData,
IPairedItemData,
IPollFunctions,
IRequestOptions,
} from 'n8n-workflow';
import { jsonParse, NodeOperationError } from 'n8n-workflow';
interface IAttachment {
url: string;
title: string;
mimetype: string;
size: number;
signedUrl?: string;
}
/**
* Make an API request to NocoDB
*
*/
export async function apiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: object,
query?: IDataObject,
uri?: string,
option: IDataObject = {},
): Promise<any> {
const authenticationMethod = this.getNodeParameter('authentication', 0) as string;
const credentials = await this.getCredentials(authenticationMethod);
if (credentials === undefined) {
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
}
const baseUrl = credentials.host as string;
query = query || {};
if (!uri) {
uri = baseUrl.endsWith('/') ? `${baseUrl.slice(0, -1)}${endpoint}` : `${baseUrl}${endpoint}`;
}
const options: IRequestOptions = {
method,
body,
qs: query,
uri,
json: true,
};
if (Object.keys(option).length !== 0) {
Object.assign(options, option);
}
if (Object.keys(body).length === 0) {
delete options.body;
}
return await this.helpers.requestWithAuthentication.call(this, authenticationMethod, options);
}
/**
* Make an API request to paginated NocoDB endpoint
* and return all results
*
* @param {(IHookFunctions | IExecuteFunctions)} this
*/
export async function apiRequestAllItems(
this: IHookFunctions | IExecuteFunctions | IPollFunctions,
method: IHttpRequestMethods,
endpoint: string,
body: IDataObject,
query?: IDataObject,
): Promise<any> {
const version = this.getNode().typeVersion;
if (query === undefined) {
query = {};
}
query.limit = 100;
query.offset = query?.offset ? (query.offset as number) : 0;
const returnData: IDataObject[] = [];
let responseData;
do {
responseData = await apiRequest.call(this, method, endpoint, body, query);
version === 1
? returnData.push(...(responseData as IDataObject[]))
: returnData.push(...(responseData.list as IDataObject[]));
query.offset += query.limit;
} while (version === 1 ? responseData.length !== 0 : responseData.pageInfo.isLastPage !== true);
return returnData;
}
export async function downloadRecordAttachments(
this: IExecuteFunctions | IPollFunctions,
records: IDataObject[],
fieldNames: string[],
pairedItem?: IPairedItemData[],
): Promise<INodeExecutionData[]> {
const elements: INodeExecutionData[] = [];
for (const record of records) {
const element: INodeExecutionData = { json: {}, binary: {} };
if (pairedItem) {
element.pairedItem = pairedItem;
}
element.json = record as unknown as IDataObject;
for (const fieldName of fieldNames) {
let attachments = record[fieldName] as IAttachment[];
if (typeof attachments === 'string') {
attachments = jsonParse<IAttachment[]>(record[fieldName] as string);
}
if (record[fieldName]) {
for (const [index, attachment] of attachments.entries()) {
const attachmentUrl = attachment.signedUrl || attachment.url;
const file: Buffer = await apiRequest.call(this, 'GET', '', {}, {}, attachmentUrl, {
json: false,
encoding: null,
});
element.binary![`${fieldName}_${index}`] = await this.helpers.prepareBinaryData(
Buffer.from(file),
attachment.title,
attachment.mimetype,
);
}
}
}
if (Object.keys(element.binary as IBinaryKeyData).length === 0) {
delete element.binary;
}
elements.push(element);
}
return elements;
}
@@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.nocoDb",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Data & Storage"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/nocodb/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.nocodb/"
}
],
"generic": []
}
}
@@ -0,0 +1,771 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import type {
IDataObject,
IExecuteFunctions,
IHttpRequestMethods,
ILoadOptionsFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import { apiRequest, apiRequestAllItems, downloadRecordAttachments } from './GenericFunctions';
import { operationFields } from './OperationDescription';
export class NocoDB implements INodeType {
description: INodeTypeDescription = {
displayName: 'NocoDB',
name: 'nocoDb',
icon: 'file:nocodb.svg',
group: ['input'],
version: [1, 2, 3],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Read, update, write and delete data from NocoDB',
defaults: {
name: 'NocoDB',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
usableAsTool: true,
credentials: [
{
name: 'nocoDb',
required: true,
displayOptions: {
show: {
authentication: ['nocoDb'],
},
},
},
{
name: 'nocoDbApiToken',
required: true,
displayOptions: {
show: {
authentication: ['nocoDbApiToken'],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'API Token',
value: 'nocoDbApiToken',
},
{
name: 'User Token',
value: 'nocoDb',
},
],
default: 'nocoDb',
},
{
displayName: 'API Version',
name: 'version',
type: 'options',
isNodeSetting: true,
options: [
{
name: 'Before v0.90.0',
value: 1,
},
{
name: 'v0.90.0 Onwards',
value: 2,
},
{
name: 'v0.200.0 Onwards',
value: 3,
},
],
displayOptions: {
show: {
'@version': [1],
},
},
default: 1,
},
{
displayName: 'API Version',
name: 'version',
type: 'options',
isNodeSetting: true,
options: [
{
name: 'Before v0.90.0',
value: 1,
},
{
name: 'v0.90.0 Onwards',
value: 2,
},
{
name: 'v0.200.0 Onwards',
value: 3,
},
],
displayOptions: {
show: {
'@version': [2],
},
},
default: 2,
},
{
displayName: 'API Version',
name: 'version',
type: 'options',
isNodeSetting: true,
options: [
{
name: 'Before v0.90.0',
value: 1,
},
{
name: 'v0.90.0 Onwards',
value: 2,
},
{
name: 'v0.200.0 Onwards',
value: 3,
},
],
displayOptions: {
show: {
'@version': [3],
},
},
default: 3,
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Row',
value: 'row',
},
],
default: 'row',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['row'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a row',
action: 'Create a row',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a row',
action: 'Delete a row',
},
{
name: 'Get',
value: 'get',
description: 'Retrieve a row',
action: 'Get a row',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Retrieve many rows',
action: 'Get many rows',
},
{
name: 'Update',
value: 'update',
description: 'Update a row',
action: 'Update a row',
},
],
default: 'get',
},
...operationFields,
],
};
methods = {
loadOptions: {
async getWorkspaces(this: ILoadOptionsFunctions) {
try {
const requestMethod = 'GET';
const endpoint = '/api/v1/workspaces/';
const responseData = await apiRequest.call(this, requestMethod, endpoint, {}, {});
return responseData.list.map((i: IDataObject) => ({ name: i.title, value: i.id }));
} catch (e) {
return [{ name: 'No Workspace', value: 'none' }];
}
},
async getBases(this: ILoadOptionsFunctions) {
const version = this.getNodeParameter('version', 0) as number;
const workspaceId = this.getNodeParameter('workspaceId', 0) as string;
try {
if (workspaceId && workspaceId !== 'none') {
const requestMethod = 'GET';
const endpoint = `/api/v1/workspaces/${workspaceId}/bases/`;
const responseData = await apiRequest.call(this, requestMethod, endpoint, {}, {});
return responseData.list.map((i: IDataObject) => ({ name: i.title, value: i.id }));
} else {
const requestMethod = 'GET';
const endpoint = version === 3 ? '/api/v2/meta/bases/' : '/api/v1/db/meta/projects/';
const responseData = await apiRequest.call(this, requestMethod, endpoint, {}, {});
return responseData.list.map((i: IDataObject) => ({ name: i.title, value: i.id }));
}
} catch (e) {
throw new NodeOperationError(
this.getNode(),
new Error(`Error while fetching ${version === 3 ? 'bases' : 'projects'}!`, {
cause: e,
}),
{
level: 'warning',
},
);
}
},
// This only supports using the Base ID
async getTables(this: ILoadOptionsFunctions) {
const version = this.getNodeParameter('version', 0) as number;
const baseId = this.getNodeParameter('projectId', 0) as string;
if (baseId) {
try {
const requestMethod = 'GET';
const endpoint =
version === 3
? `/api/v2/meta/bases/${baseId}/tables`
: `/api/v1/db/meta/projects/${baseId}/tables`;
const responseData = await apiRequest.call(this, requestMethod, endpoint, {}, {});
return responseData.list.map((i: IDataObject) => ({ name: i.title, value: i.id }));
} catch (e) {
throw new NodeOperationError(
this.getNode(),
new Error('Error while fetching tables!', { cause: e }),
{
level: 'warning',
},
);
}
} else {
throw new NodeOperationError(
this.getNode(),
`No ${version === 3 ? 'base' : 'project'} selected!`,
{
level: 'warning',
},
);
}
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
let responseData;
const version = this.getNodeParameter('version', 0) as number;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
let returnAll = false;
let requestMethod: IHttpRequestMethods = 'GET';
let qs: IDataObject = {};
let endPoint = '';
const baseId = this.getNodeParameter('projectId', 0) as string;
const table = this.getNodeParameter('table', 0) as string;
if (resource === 'row') {
if (operation === 'create') {
requestMethod = 'POST';
if (version === 1) {
endPoint = `/nc/${baseId}/api/v1/${table}/bulk`;
} else if (version === 2) {
endPoint = `/api/v1/db/data/bulk/noco/${baseId}/${table}`;
} else if (version === 3) {
endPoint = `/api/v2/tables/${table}/records`;
}
const body: IDataObject[] = [];
for (let i = 0; i < items.length; i++) {
const newItem: IDataObject = {};
const dataToSend = this.getNodeParameter('dataToSend', i) as
| 'defineBelow'
| 'autoMapInputData';
if (dataToSend === 'autoMapInputData') {
const incomingKeys = Object.keys(items[i].json);
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
const inputDataToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
for (const key of incomingKeys) {
if (inputDataToIgnore.includes(key)) continue;
newItem[key] = items[i].json[key];
}
} else {
const fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as Array<{
fieldName: string;
binaryData: boolean;
fieldValue?: string;
binaryProperty?: string;
}>;
for (const field of fields) {
if (!field.binaryData) {
newItem[field.fieldName] = field.fieldValue;
} else if (field.binaryProperty) {
const binaryPropertyName = field.binaryProperty;
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
const dataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
const formData = {
file: {
value: dataBuffer,
options: {
filename: binaryData.fileName,
contentType: binaryData.mimeType,
},
},
json: JSON.stringify({
api: 'xcAttachmentUpload',
project_id: baseId,
dbAlias: 'db',
args: {},
}),
};
let postUrl = '';
if (version === 1) {
postUrl = '/dashboard';
} else if (version === 2) {
postUrl = '/api/v1/db/storage/upload';
} else if (version === 3) {
postUrl = '/api/v2/storage/upload';
}
responseData = await apiRequest.call(
this,
'POST',
postUrl,
{},
version === 3 ? { base_id: baseId } : { project_id: baseId },
undefined,
{
formData,
},
);
newItem[field.fieldName] = JSON.stringify(
Array.isArray(responseData) ? responseData : [responseData],
);
}
}
}
body.push(newItem);
}
try {
responseData = await apiRequest.call(this, requestMethod, endPoint, body, qs);
if (version === 3) {
for (let i = body.length - 1; i >= 0; i--) {
body[i] = { ...body[i], ...responseData[i] };
}
returnData.push(...body);
} else {
// Calculate ID manually and add to return data
let id = responseData[0];
for (let i = body.length - 1; i >= 0; i--) {
body[i].id = id--;
}
returnData.push(...body);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.toString() });
}
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
if (operation === 'delete') {
requestMethod = 'DELETE';
let primaryKey = 'id';
if (version === 1) {
endPoint = `/nc/${baseId}/api/v1/${table}/bulk`;
} else if (version === 2) {
endPoint = `/api/v1/db/data/bulk/noco/${baseId}/${table}`;
primaryKey = this.getNodeParameter('primaryKey', 0) as string;
if (primaryKey === 'custom') {
primaryKey = this.getNodeParameter('customPrimaryKey', 0) as string;
}
} else if (version === 3) {
endPoint = `/api/v2/tables/${table}/records`;
primaryKey = this.getNodeParameter('primaryKey', 0) as string;
if (primaryKey === 'custom') {
primaryKey = this.getNodeParameter('customPrimaryKey', 0) as string;
}
}
const body: IDataObject[] = [];
for (let i = 0; i < items.length; i++) {
const id = this.getNodeParameter('id', i) as string;
body.push({ [primaryKey]: id });
}
try {
responseData = (await apiRequest.call(this, requestMethod, endPoint, body, qs)) as any[];
if (version === 1) {
returnData.push(...items.map((item) => item.json));
} else if (version === 2) {
returnData.push(
...responseData.map((result: number, index: number) => {
if (result === 0) {
const errorMessage = `The row with the ID "${body[index].id}" could not be deleted. It probably doesn't exist.`;
if (this.continueOnFail()) {
return { error: errorMessage };
}
throw new NodeApiError(
this.getNode(),
{ message: errorMessage },
{ message: errorMessage, itemIndex: index },
);
}
return {
success: true,
};
}),
);
} else if (version === 3) {
returnData.push(...responseData);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.toString() });
}
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
if (operation === 'getAll') {
const data = [];
const downloadAttachments = this.getNodeParameter('downloadAttachments', 0) as boolean;
try {
for (let i = 0; i < items.length; i++) {
requestMethod = 'GET';
if (version === 1) {
endPoint = `/nc/${baseId}/api/v1/${table}`;
} else if (version === 2) {
endPoint = `/api/v1/db/data/noco/${baseId}/${table}`;
} else if (version === 3) {
endPoint = `/api/v2/tables/${table}/records`;
}
returnAll = this.getNodeParameter('returnAll', 0);
qs = this.getNodeParameter('options', i, {});
if (qs.sort) {
const properties = (qs.sort as IDataObject).property as Array<{
field: string;
direction: string;
}>;
qs.sort = properties
.map((prop) => `${prop.direction === 'asc' ? '' : '-'}${prop.field}`)
.join(',');
}
if (qs.fields) {
qs.fields = (qs.fields as IDataObject[]).join(',');
}
if (returnAll) {
responseData = await apiRequestAllItems.call(this, requestMethod, endPoint, {}, qs);
} else {
qs.limit = this.getNodeParameter('limit', 0);
responseData = await apiRequest.call(this, requestMethod, endPoint, {}, qs);
if (version === 2 || version === 3) {
responseData = responseData.list;
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
if (downloadAttachments) {
const downloadFieldNames = (
this.getNodeParameter('downloadFieldNames', 0) as string
).split(',');
const response = await downloadRecordAttachments.call(
this,
responseData as IDataObject[],
downloadFieldNames,
[{ item: i }],
);
data.push(...response);
}
}
if (downloadAttachments) {
return [data];
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: error.toString() } });
} else {
throw error;
}
}
return [returnData as INodeExecutionData[]];
}
if (operation === 'get') {
requestMethod = 'GET';
const newItems: INodeExecutionData[] = [];
for (let i = 0; i < items.length; i++) {
try {
const id = this.getNodeParameter('id', i) as string;
if (version === 1) {
endPoint = `/nc/${baseId}/api/v1/${table}/${id}`;
} else if (version === 2) {
endPoint = `/api/v1/db/data/noco/${baseId}/${table}/${id}`;
} else if (version === 3) {
endPoint = `/api/v2/tables/${table}/records/${id}`;
}
responseData = await apiRequest.call(this, requestMethod, endPoint, {}, qs);
if (version === 2) {
if (Object.keys(responseData as IDataObject).length === 0) {
// Get did fail
const errorMessage = `The row with the ID "${id}" could not be queried. It probably doesn't exist.`;
if (this.continueOnFail()) {
newItems.push({ json: { error: errorMessage } });
continue;
}
throw new NodeApiError(
this.getNode(),
{ message: errorMessage },
{ message: errorMessage, itemIndex: i },
);
}
}
const downloadAttachments = this.getNodeParameter('downloadAttachments', i) as boolean;
if (downloadAttachments) {
const downloadFieldNames = (
this.getNodeParameter('downloadFieldNames', i) as string
).split(',');
const data = await downloadRecordAttachments.call(
this,
[responseData as IDataObject],
downloadFieldNames,
[{ item: i }],
);
const newItem = {
binary: data[0].binary,
json: {},
};
const executionData = this.helpers.constructExecutionMetaData(
[newItem] as INodeExecutionData[],
{ itemData: { item: i } },
);
newItems.push(...executionData);
} else {
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
newItems.push(...executionData);
}
} catch (error) {
if (this.continueOnFail()) {
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.toString() }),
{ itemData: { item: i } },
);
newItems.push(...executionData);
continue;
}
throw new NodeApiError(this.getNode(), error as JsonObject, { itemIndex: i });
}
}
return [newItems];
}
if (operation === 'update') {
requestMethod = 'PATCH';
let primaryKey = 'id';
if (version === 1) {
endPoint = `/nc/${baseId}/api/v1/${table}/bulk`;
requestMethod = 'PUT';
} else if (version === 2) {
endPoint = `/api/v1/db/data/bulk/noco/${baseId}/${table}`;
primaryKey = this.getNodeParameter('primaryKey', 0) as string;
if (primaryKey === 'custom') {
primaryKey = this.getNodeParameter('customPrimaryKey', 0) as string;
}
} else if (version === 3) {
endPoint = `/api/v2/tables/${table}/records`;
}
const body: IDataObject[] = [];
for (let i = 0; i < items.length; i++) {
const id = version === 3 ? null : (this.getNodeParameter('id', i) as string);
const newItem: IDataObject = version === 3 ? {} : { [primaryKey]: id };
const dataToSend = this.getNodeParameter('dataToSend', i) as
| 'defineBelow'
| 'autoMapInputData';
if (dataToSend === 'autoMapInputData') {
const incomingKeys = Object.keys(items[i].json);
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
const inputDataToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
for (const key of incomingKeys) {
if (inputDataToIgnore.includes(key)) continue;
newItem[key] = items[i].json[key];
}
} else {
const fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as Array<{
fieldName: string;
binaryData: boolean;
fieldValue?: string;
binaryProperty?: string;
}>;
for (const field of fields) {
if (!field.binaryData) {
newItem[field.fieldName] = field.fieldValue;
} else if (field.binaryProperty) {
const binaryPropertyName = field.binaryProperty;
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
const dataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
const formData = {
file: {
value: dataBuffer,
options: {
filename: binaryData.fileName,
contentType: binaryData.mimeType,
},
},
json: JSON.stringify({
api: 'xcAttachmentUpload',
project_id: baseId,
dbAlias: 'db',
args: {},
}),
};
let postUrl = '';
if (version === 1) {
postUrl = '/dashboard';
} else if (version === 2) {
postUrl = '/api/v1/db/storage/upload';
} else if (version === 3) {
postUrl = '/api/v2/storage/upload';
}
responseData = await apiRequest.call(
this,
'POST',
postUrl,
{},
version === 3 ? { base_id: baseId } : { project_id: baseId },
undefined,
{
formData,
},
);
newItem[field.fieldName] = JSON.stringify(
Array.isArray(responseData) ? responseData : [responseData],
);
}
}
}
body.push(newItem);
}
try {
responseData = (await apiRequest.call(this, requestMethod, endPoint, body, qs)) as any[];
if (version === 1) {
returnData.push(...body);
} else if (version === 2) {
returnData.push(
...responseData.map((result: number, index: number) => {
if (result === 0) {
const errorMessage = `The row with the ID "${body[index].id}" could not be updated. It probably doesn't exist.`;
if (this.continueOnFail()) {
return { error: errorMessage };
}
throw new NodeApiError(
this.getNode(),
{ message: errorMessage },
{ message: errorMessage, itemIndex: index },
);
}
return {
success: true,
};
}),
);
} else if (version === 3) {
for (let i = body.length - 1; i >= 0; i--) {
body[i] = { ...body[i], ...responseData[i] };
}
returnData.push(...body);
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.toString() });
}
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}
@@ -0,0 +1,532 @@
import type { INodeProperties } from 'n8n-workflow';
export const operationFields: INodeProperties[] = [
// ----------------------------------
// Shared
// ----------------------------------
{
displayName: 'Workspace Name or ID',
name: 'workspaceId',
type: 'options',
default: 'none',
displayOptions: {
show: {
version: [3],
},
},
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getWorkspaces',
},
},
{
displayName: 'Base Name or ID',
name: 'projectId',
type: 'options',
default: '',
displayOptions: {
show: {
version: [3],
},
},
required: true,
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsDependsOn: ['workspaceId'],
loadOptionsMethod: 'getBases',
},
},
{
displayName: 'Project ID',
name: 'projectId',
type: 'string',
default: '',
displayOptions: {
show: {
version: [1],
},
},
required: true,
description: 'The ID of the project',
},
{
displayName: 'Project Name or ID',
name: 'projectId',
type: 'options',
default: '',
displayOptions: {
show: {
version: [2],
},
},
required: true,
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getBases',
},
},
{
displayName: 'Table Name or ID',
name: 'table',
type: 'options',
default: '',
displayOptions: {
show: {
version: [2, 3],
},
},
required: true,
description:
'The table to operate on. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
typeOptions: {
loadOptionsDependsOn: ['projectId'],
loadOptionsMethod: 'getTables',
},
},
{
displayName: 'Table',
name: 'table',
type: 'string',
default: '',
displayOptions: {
show: {
version: [1],
},
},
required: true,
description: 'The name of the table',
},
{
displayName: 'Primary Key Type',
name: 'primaryKey',
type: 'options',
default: 'id',
options: [
{
name: 'Default',
value: 'id',
description:
'Default, added when table was created from UI by those options: Create new table / Import from Excel / Import from CSV',
},
{
name: 'Imported From Airtable',
value: 'ncRecordId',
description: 'Select if table was imported from Airtable',
},
{
name: 'Custom',
value: 'custom',
description:
'When connecting to existing external database as existing primary key field is retained as is, enter the name of the primary key field below',
},
],
displayOptions: {
show: {
version: [1, 2],
operation: ['delete', 'update'],
},
},
},
{
displayName: 'Primary Key Type',
name: 'primaryKey',
type: 'options',
default: 'id',
options: [
{
name: 'Default',
value: 'id',
description:
'Default, added when table was created from UI by those options: Create new table / Import from Excel / Import from CSV',
},
{
name: 'Imported From Airtable',
value: 'ncRecordId',
description: 'Select if table was imported from Airtable',
},
{
name: 'Custom',
value: 'custom',
description:
'When connecting to existing external database as existing primary key field is retained as is, enter the name of the primary key field below',
},
],
displayOptions: {
show: {
version: [3],
operation: ['delete'],
},
},
},
{
displayName: 'Field Name',
name: 'customPrimaryKey',
type: 'string',
default: '',
displayOptions: {
show: {
version: [1, 2],
operation: ['delete', 'update'],
primaryKey: ['custom'],
},
},
},
{
displayName: 'Field Name',
name: 'customPrimaryKey',
type: 'string',
default: '',
displayOptions: {
show: {
version: [3],
operation: ['delete'],
primaryKey: ['custom'],
},
},
},
{
displayName: 'Row ID Value',
name: 'id',
type: 'string',
default: '',
required: true,
description: 'The value of the ID field',
displayOptions: {
show: {
version: [1, 2],
operation: ['delete', 'get', 'update'],
},
},
},
{
displayName: 'Row ID Value',
name: 'id',
type: 'string',
default: '',
required: true,
description: 'The value of the ID field',
displayOptions: {
show: {
version: [3],
operation: ['delete', 'get'],
},
},
},
// ----------------------------------
// delete
// ----------------------------------
// ----------------------------------
// getAll
// ----------------------------------
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: ['getAll'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: ['getAll'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Download Attachments',
name: 'downloadAttachments',
type: 'boolean',
displayOptions: {
show: {
operation: ['getAll'],
},
},
default: false,
description: "Whether the attachment fields define in 'Download Fields' will be downloaded",
},
{
displayName: 'Download Fields',
name: 'downloadFieldNames',
type: 'string',
required: true,
displayOptions: {
show: {
operation: ['getAll'],
downloadAttachments: [true],
},
},
default: '',
description:
"Name of the fields of type 'attachment' that should be downloaded. Multiple ones can be defined separated by comma. Case sensitive.",
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: ['getAll'],
},
},
default: {},
placeholder: 'Add option',
options: [
{
displayName: 'View ID',
name: 'viewId',
type: 'string',
typeOptions: {
multipleValues: false,
},
default: '',
placeholder: 'View ID',
description: 'The select fields of the returned rows',
},
{
displayName: 'Fields',
name: 'fields',
type: 'string',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add Field',
},
default: [],
placeholder: 'Name',
description: 'The select fields of the returned rows',
},
{
displayName: 'Sort',
name: 'sort',
placeholder: 'Add Sort Rule',
description: 'The sorting rules for the returned rows',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'property',
displayName: 'Property',
values: [
{
displayName: 'Field',
name: 'field',
type: 'string',
default: '',
description: 'Name of the field to sort on',
},
{
displayName: 'Direction',
name: 'direction',
type: 'options',
options: [
{
name: 'ASC',
value: 'asc',
description: 'Sort in ascending order (small -> large)',
},
{
name: 'DESC',
value: 'desc',
description: 'Sort in descending order (large -> small)',
},
],
default: 'asc',
description: 'The sort direction',
},
],
},
],
},
{
displayName: 'Filter By Formula',
name: 'where',
type: 'string',
default: '',
placeholder: '(name,like,example%)~or(name,eq,test)',
description: 'A formula used to filter rows',
},
],
},
// ----------------------------------
// get
// ----------------------------------
{
displayName: 'Download Attachments',
name: 'downloadAttachments',
type: 'boolean',
displayOptions: {
show: {
operation: ['get'],
},
},
default: false,
description: "Whether the attachment fields define in 'Download Fields' will be downloaded",
},
{
displayName: 'Download Fields',
name: 'downloadFieldNames',
type: 'string',
required: true,
displayOptions: {
show: {
operation: ['get'],
downloadAttachments: [true],
},
},
default: '',
description:
"Name of the fields of type 'attachment' that should be downloaded. Multiple ones can be defined separated by comma. Case sensitive.",
},
// ----------------------------------
// update
// ----------------------------------
// ----------------------------------
// Shared
// ----------------------------------
{
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'],
},
},
default: 'defineBelow',
description: 'Whether to insert the input data this node receives in the new row',
},
{
displayName:
"In this mode, make sure the incoming data fields are named the same as the columns in NocoDB. (Use an 'Edit Fields' node before this node to change them if required.)",
name: 'info',
type: 'notice',
default: '',
displayOptions: {
show: {
dataToSend: ['autoMapInputData'],
},
},
},
{
displayName: 'This operation requires the primary key to be included for each row.',
name: 'info',
type: 'notice',
default: '',
displayOptions: {
show: {
operation: ['update'],
version: [3],
},
},
},
{
displayName: 'Inputs to Ignore',
name: 'inputsToIgnore',
type: 'string',
displayOptions: {
show: {
operation: ['create', 'update'],
dataToSend: ['autoMapInputData'],
},
},
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',
typeOptions: {
multipleValueButtonText: 'Add Field to Send',
multipleValues: true,
},
displayOptions: {
show: {
operation: ['create', 'update'],
dataToSend: ['defineBelow'],
},
},
default: {},
options: [
{
displayName: 'Field',
name: 'fieldValues',
values: [
{
displayName: 'Field Name',
name: 'fieldName',
type: 'string',
default: '',
},
{
displayName: 'Is Binary File',
name: 'binaryData',
type: 'boolean',
default: false,
description:
'Whether the field data to set is binary and should be taken from a binary property',
},
{
displayName: 'Field Value',
name: 'fieldValue',
type: 'string',
default: '',
displayOptions: {
show: {
binaryData: [false],
},
},
},
{
displayName: 'Take Input From Field',
name: 'binaryProperty',
type: 'string',
description: 'The field containing the binary file data to be uploaded',
default: '',
displayOptions: {
show: {
binaryData: [true],
},
},
},
],
},
],
},
];
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" fill="none"><rect width="200" height="200" fill="#3D43D5" rx="25"/><path fill="#fff" d="m38 93.907 29.76 29.006v45.92H38zm125-61.665v125.417c0 8.078-4.498 11.341-11.742 11.341-1.333 0-3.725-.501-5.793-2.502L38 65.328V42.84C38 34.763 41.363 31.5 49.723 31.5c1.333 0 4.01.52 6.06 2.502l77.438 71.923V32.242z"/></svg>

After

Width:  |  Height:  |  Size: 379 B