Run Script

Execute custom scripts as part of NocoDB workflows.

Available on NocoDB cloud Business plan onwards and licensed on-premise deployments.

The Run Script action node in NocoDB workflows allows you to execute custom JavaScript logic as part of an automated workflow. This node is useful when built-in actions are not sufficient, and you need fine-grained control over tables, fields, records, or workflow data.

Run Script Editor

Configuration

Inputs

  • Define input variables that can be accessed inside the script using input.config().
  • Inputs can be static values or mapped from outputs of previous workflow nodes.

Script

  • Write JavaScript code to perform custom logic.
  • Scripts can read workflow inputs, interact with tables, and perform conditional logic.
  • Use Edit code to open the script editor.

Test Step

  • Use Test this action to execute the script with the configured inputs.
  • Test results and logs are displayed in the right panel.

Script Editor

Use the script editor to write and test your JavaScript code. The editor provides syntax highlighting and error checking to help you develop your script. Debugging information is available on the panel to the right.

Run Script Node

Available APIs

Workflow scripts have access to a subset of the NocoDB Scripting APIs. Since workflows run in an automated context without user interaction, some APIs are not available.

Input API

Access input variables configured in the workflow node:

// Get all input values as an object
const config = input.config();

// Access specific input values
const recordId = input.config().recordId;
const tableName = input.config().tableName;
const threshold = input.config().threshold;

Output API

Set output values that can be used by downstream workflow nodes:

// Set a single output value
output.set('processedCount', 42);
output.set('status', 'success');
output.set('results', { updated: 10, skipped: 5 });

// Output values are available to subsequent nodes in the workflow

Other APIs

The following APIs work the same as in interactive scripts. Refer to the NocoDB Scripting Guide for full documentation:

  • Base & Table operations - Full CRUD operations on tables, views, fields, and records
  • HTTP fetch - Make external API calls
  • Console logging - console.log(), console.warn(), console.error() for debugging
  • Collaborators - Access base collaborators via base.activeCollaborators
  • UITypes - Field type constants for type checking

APIs Not Available in Workflows

The following APIs from interactive scripts are not available in workflow scripts:

APIReason
input.textAsync(), input.buttonsAsync(), input.selectAsync(), etc.Workflows run without user interaction
input.tableAsync(), input.viewAsync(), input.fieldAsync(), input.recordAsync()Interactive selection not available
input.fileAsync()File uploads not available in automated context
output.text(), output.markdown(), output.table(), output.inspect()No UI display in workflows
output.clear()No UI to clear
script.step(), script.clear()Script steps are for interactive feedback
session.currentUserNo user session in automated workflows
cursorNo UI context for cursor position
input.config({...}) with form builderUse workflow node inputs instead

How it Works

  1. When the workflow reaches the Run Script node, NocoDB initializes a sandbox execution environment.
  2. Input variables configured in the node are made available via input.config().
  3. The script executes with full access to the base's tables and records.
  4. Output values set via output.set() become available to downstream nodes.
  5. Once execution completes, the workflow proceeds to the next node.

Examples

Process Records Based on Criteria

const config = input.config();
const table = base.getTable(config.tableName);

// Find records that need processing
const records = await table.selectRecordsAsync({
  where: '(Status,eq,Pending)',
  fields: ['Title', 'Status', 'Priority']
});

let processedCount = 0;

for (const record of records.records) {
  const priority = record.getCellValue('Priority');

  if (priority === 'High') {
    await table.updateRecordAsync(record.id, {
      'Status': 'In Progress'
    });
    processedCount++;
  }
}

output.set('processedCount', processedCount);
output.set('totalFound', records.records.length);

Call External API and Update Records

const config = input.config();
const recordId = config.recordId;

const table = base.getTable('Orders');
const record = await table.selectRecordAsync(recordId);
const orderId = record.getCellValue('External Order ID');

// Call external API
const response = await fetch(`https://api.shipping.com/status/${orderId}`, {
  headers: { 'Authorization': `Bearer ${config.apiKey}` }
});

if (response.status === 200) {
  const shippingStatus = response.data.status;

  await table.updateRecordAsync(recordId, {
    'Shipping Status': shippingStatus,
    'Last Updated': new Date().toISOString()
  });

  output.set('status', 'success');
  output.set('shippingStatus', shippingStatus);
} else {
  output.set('status', 'error');
  output.set('errorMessage', response.statusText);
}

Aggregate Data Across Tables

const ordersTable = base.getTable('Orders');
const customersTable = base.getTable('Customers');

const customerId = input.config().customerId;

// Get all orders for a customer
const orders = await ordersTable.selectRecordsAsync({
  where: `(Customer ID,eq,${customerId})`,
  fields: ['Order Total', 'Order Date', 'Status']
});

// Calculate totals
let totalSpent = 0;
let orderCount = 0;

for (const order of orders.records) {
  if (order.getCellValue('Status') === 'Completed') {
    totalSpent += order.getCellValue('Order Total') || 0;
    orderCount++;
  }
}

// Update customer record
await customersTable.updateRecordAsync(customerId, {
  'Total Spent': totalSpent,
  'Order Count': orderCount,
  'Last Calculated': new Date().toISOString()
});

output.set('totalSpent', totalSpent);
output.set('orderCount', orderCount);

Create Records in Bulk

const config = input.config();
const sourceTable = base.getTable(config.sourceTable);
const targetTable = base.getTable(config.targetTable);

const records = await sourceTable.selectRecordsAsync({
  where: '(Processed,eq,false)',
  pageSize: 10
});

const createdIds = [];

for (const record of records.records) {
  const newId = await targetTable.createRecordAsync({
    'Title': record.getCellValue('Title'),
    'Description': record.getCellValue('Description'),
    'Source Record ID': record.id
  });
  createdIds.push(newId);

  // Mark as processed
  await sourceTable.updateRecordAsync(record.id, {
    'Processed': true
  });
}

output.set('createdCount', createdIds.length);
output.set('createdIds', createdIds);

Use Cases

  • Perform complex data transformations or calculations that are not supported by standard nodes.
  • Integrate with external APIs or services.
  • Implement custom validation or business logic.
  • Create or modify records dynamically based on complex conditions.
  • Aggregate data across multiple tables.
  • Generate computed values for downstream workflow nodes.