Record

Documentation for the NocoDBRecord object in NocoDB Scripts

The NocoDBRecord object represents a single row of data in a NocoDB table and provides methods to access and work with the cell values within that record.

Overview

Records are typically retrieved from tables or views using query methods like selectRecordsAsync() or selectRecordAsync(). Each NocoDBRecord object provides access to:

  • The record's unique identifier
  • The record's primary display value (name)
  • All cell values in the record
  • Formatted string representations of cell values

Working with NocoDBRecord objects is central to most NocoDB scripts, as they allow you to read, manipulate, and interpret the data in your base with rich type support.

Properties

PropertyTypeDescription
idstringThe unique identifier for this record
namestringThe primary display value for this record (derived from display field)

Methods

getCellValue

Retrieves the value of a specific cell in the record.

Parameters:

  • fieldOrFieldIdOrName (Field | string): The field, field ID, or field name to get the value for

Returns: The value of the cell in a format determined by the field type (see Cell Value Types section)

Throws: Error if the field is not found or not included in the query

Example:

// Get the Projects table
const projectsTable = base.getTable('Projects');

// Query a specific record
const projectRecord = await projectsTable.selectRecordAsync('rec123456');

if (projectRecord) {
  // Get cell values using field names
  const projectName = projectRecord.getCellValue('Project Name');
  const dueDate = projectRecord.getCellValue('Due Date');
  const status = projectRecord.getCellValue('Status');
  
  output.text(`Project: ${projectName}`);
  output.text(`Due Date: ${dueDate}`);
  output.text(`Status: ${status}`);
  
  // Get a cell value using a field object
  const assignedToField = projectsTable.getField('Assigned To');
  const assignedTo = projectRecord.getCellValue(assignedToField);
  
  if (assignedTo) {
    output.text(`Assigned To: ${assignedTo.name}`);
  } else {
    output.text('This project is unassigned.');
  }
}

getCellValueAsString

Gets a cell value formatted as a string, applying appropriate formatting based on the field type.

Parameters:

  • fieldOrFieldIdOrName (Field | string): The field, field ID, or field name to get the value for

Returns: string - A string representation of the cell value with proper field-type formatting

Throws: Error if the field is not found or not included in the query

Note: This method provides comprehensive formatting for all field types including currency, dates, durations, percentages, and more.

Example:

// Get the Invoices table
const invoicesTable = base.getTable('Invoices');

// Query a specific record
const invoiceRecord = await invoicesTable.selectRecordAsync('rec123456');

if (invoiceRecord) {
  // Get formatted string values
  const invoiceNumber = invoiceRecord.getCellValueAsString('Invoice Number');
  const amount = invoiceRecord.getCellValueAsString('Amount'); // Formatted as currency
  const date = invoiceRecord.getCellValueAsString('Date'); // Formatted as date
  const isPaid = invoiceRecord.getCellValueAsString('Paid'); // "Checked" or "Unchecked"
  const attachments = invoiceRecord.getCellValueAsString('Attachments'); // Comma-separated file names
  
  // Output the formatted values
  output.text(`Invoice: ${invoiceNumber}`);
  output.text(`Amount: ${amount}`);
  output.text(`Date: ${date}`);
  output.text(`Paid: ${isPaid}`);
  output.text(`Attachments: ${attachments}`);
}

Cell Value Types

Different field types return values in specific formats when using getCellValue(). Here's a reference for the most common field types:

Text Fields

// Single Line Text & Long Text
const title = record.getCellValue('Title'); // string or null

Number Fields

// Number, Decimal, Currency, Percent
const amount = record.getCellValue('Amount'); // number or null
const quantity = record.getCellValue('Quantity'); // number or null
const price = record.getCellValue('Price'); // number or null
const discount = record.getCellValue('Discount'); // number or null (without % sign)

Boolean Fields

// Checkbox
const isCompleted = record.getCellValue('Completed'); // boolean or null

Date and Time Fields

// Date
const dueDate = record.getCellValue('Due Date'); // string in ISO format or null
// e.g., "2023-08-15"

// DateTime
const createdAt = record.getCellValue('Created At'); // string in ISO format or null
// e.g., "2023-08-15T14:30:00.000Z"

// Time
const startTime = record.getCellValue('Start Time'); // string in ISO format or null
// e.g., "14:30:00.000"

Selection Fields

// Single Select
const status = record.getCellValue('Status'); // string or null
// e.g., "In Progress"

// Multi Select
const tags = record.getCellValue('Tags'); // array of strings or empty array
// e.g., ["Urgent", "Bug"]

Relationship Fields

// LinkToAnotherRecord (one to one / belongs to relationship)
const project = record.getCellValue('Project'); // NocoDBRecord object or null
if (project) {
  output.text(`Project ID: ${project.id}`);
  output.text(`Project Name: ${project.name}`);
}

// LinkToAnotherRecord (many-to-many / has-many relationship)
const team = record.getCellValue('Team Members'); // RecordQueryResult object or null
while (team.hasMoreRecords) {
  await team.loadMoreRecords();
}

output.text(`Team members: ${team.records.length}`);

// Links (new field type for relationships)
const linkedProjects = record.getCellValue('Related Projects'); // Can return count or records
// For many-to-many/has-many: returns RecordQueryResult object or null
// For belongs-to/one-to-one: returns NocoDBRecord object or null

Attachment Field

// Attachment
const documents = record.getCellValue('Documents'); // Array of attachment objects or empty array

/*
Each attachment object has:
{
  url: string,
  title: string,
  mimetype: string,
  size: number,
  signedUrl?: string,
  thumbnails?: {
    tiny: { signedUrl: string },
    small: { signedUrl: string },
    card_cover: { signedUrl: string }
  }
}
*/

if (documents && documents.length > 0) {
  for (const doc of documents) {
    output.text(`File: ${doc.title}`);
    output.text(`Type: ${doc.mimetype}`);
    output.text(`Size: ${doc.size} bytes`);
    output.text(`URL: ${doc.url}`);
  }
}

User Field

// User (single)
const assignee = record.getCellValue('Assigned To'); // Collaborator object or null

if (assignee) {
  output.text(`Assignee ID: ${assignee.id}`);
  output.text(`Assignee Name: ${assignee.name}`);
  output.text(`Assignee Email: ${assignee.email}`);
}

// User (multiple)
const reviewers = record.getCellValue('Reviewers'); // Array of Collaborator objects or empty array

if (reviewers && reviewers.length > 0) {
  for (const reviewer of reviewers) {
    output.text(`Reviewer ID: ${reviewer.id}`);
    output.text(`Reviewer Name: ${reviewer.name}`);
    output.text(`Reviewer Email: ${reviewer.email}`);
  }
}

Formula Field

// Formula fields return a value of the appropriate type based on the formula's output type
const fullName = record.getCellValue('Full Name'); // string or null
const age = record.getCellValue('Age'); // number or null
const isAdult = record.getCellValue('Is Adult'); // boolean or null

Lookup Field

// Lookup fields return values of the type that is being looked up
const managerName = record.getCellValue('Manager Name'); // string or null
const departmentSize = record.getCellValue('Department Size'); // number or null

// Lookup to multiple values
const teamEmails = record.getCellValue('Team Emails'); // Array of values or empty array

Rollup Field

// Rollup fields return values based on the rollup function and field type
const totalHours = record.getCellValue('Total Hours'); // number or null
const averageScore = record.getCellValue('Average Score'); // number or null
const taskCount = record.getCellValue('Task Count'); // number or null

Working with Records

Reading Record Data

// Get the Tasks table
const tasksTable = base.getTable('Tasks');

// Get a specific record
const taskRecord = await tasksTable.selectRecordAsync('rec123456', {
  fields: ['Task Name', 'Description', 'Status', 'Due Date', 'Assigned To', 'Priority']
});

if (taskRecord) {
  // Read basic properties
  output.text(`Record ID: ${taskRecord.id}`);
  output.text(`Primary Value: ${taskRecord.name}`);
  
  // Read values using getCellValue
  const taskName = taskRecord.getCellValue('Task Name');
  const description = taskRecord.getCellValue('Description');
  const status = taskRecord.getCellValue('Status');
  const dueDate = taskRecord.getCellValue('Due Date');
  const assignedTo = taskRecord.getCellValue('Assigned To');
  const priority = taskRecord.getCellValue('Priority');
  
  // Display the task information
  output.markdown(`# Task: ${taskName}`);
  output.text(`Description: ${description || 'No description provided'}`);
  output.text(`Status: ${status || 'Not set'}`);
  output.text(`Due Date: ${dueDate ? new Date(dueDate).toLocaleDateString() : 'No due date'}`);
  
  if (assignedTo) {
    output.text(`Assigned To: ${assignedTo.name} (${assignedTo.email})`);
  } else {
    output.text('This task is unassigned.');
  }
  
  output.text(`Priority: ${priority || 'Not set'}`);
}

Comparing Records

// Get the Employees table
const employeesTable = base.getTable('Employees');

// Get two employee records
const employee1 = await employeesTable.selectRecordAsync('rec123456');
const employee2 = await employeesTable.selectRecordAsync('rec789012');

if (employee1 && employee2) {
  // Compare salary values
  const salary1 = employee1.getCellValue('Salary') || 0;
  const salary2 = employee2.getCellValue('Salary') || 0;
  
  // Compare departments
  const dept1 = employee1.getCellValue('Department');
  const dept2 = employee2.getCellValue('Department');
  
  // Compare hire dates
  const hireDate1 = employee1.getCellValue('Hire Date');
  const hireDate2 = employee2.getCellValue('Hire Date');
  
  // Output comparison
  output.markdown(`# Employee Comparison`);
  output.markdown(`## ${employee1.getCellValue('Name')} vs. ${employee2.getCellValue('Name')}`);
  
  output.text(`Salary: $${salary1.toLocaleString()} vs. $${salary2.toLocaleString()}`);
  output.text(`Department: ${dept1} vs. ${dept2}`);
  
  if (hireDate1 && hireDate2) {
    const date1 = new Date(hireDate1);
    const date2 = new Date(hireDate2);
    const seniorityDiff = Math.floor((date2 - date1) / (1000 * 60 * 60 * 24));
    
    if (seniorityDiff > 0) {
      output.text(`${employee1.getCellValue('Name')} has been with the company ${seniorityDiff} days longer.`);
    } else if (seniorityDiff < 0) {
      output.text(`${employee2.getCellValue('Name')} has been with the company ${Math.abs(seniorityDiff)} days longer.`);
    } else {
      output.text('Both employees were hired on the same day.');
    }
  }
}

Working with Linked Records

// Get the Projects and Tasks tables
const projectsTable = base.getTable('Projects');
const tasksTable = base.getTable('Tasks');

// Get a project record with its linked tasks
const projectRecord = await projectsTable.selectRecordAsync('25', {
  fields: ['Project Name', 'Description', 'Status', 'Tasks']
});
 
if (projectRecord) {
  const projectName = projectRecord.getCellValue('Project Name');
  const tasks = projectRecord.getCellValue('Tasks') || [];

  while(tasks.hasMoreRecords) {
    await tasks.loadMoreRecords()
  }
  
  output.markdown(`# Project: ${projectName}`);
  output.text(`Status: ${projectRecord.getCellValue('Status')}`);
  output.text(`Number of Tasks: ${tasks.records.length}`);
  
  if (tasks.records.length > 0) {
    // Direct access to linked record information
    output.markdown('## Task Summary');
    
    for (const task of tasks.records) {
      output.text(`- ${task.name}`);
    }
    
    // For detailed task information, we need to query each task
    output.markdown('## Detailed Task Information');
    
    // Get all task IDs
    const taskIds = tasks.recordIds
    
    // Query the tasks with additional fields
    const taskRecords = await tasksTable.selectRecordsAsync({
      fields: ['Task Name', 'Status', 'Due Date', 'Assigned To'],
      recordIds: taskIds
    });
    
    // Process each task with full details
    for (const taskRecord of taskRecords.records) {
      const taskName = taskRecord.getCellValue('Task Name');
      const status = taskRecord.getCellValue('Status');
      const dueDate = taskRecord.getCellValue('Due Date');
      const assignedTo = taskRecord.getCellValue('Assigned To');
      
      output.markdown(`### ${taskName}`);
      output.text(`Status: ${status || 'Not set'}`);
      output.text(`Due Date: ${dueDate ? new Date(dueDate).toLocaleDateString() : 'No due date'}`);
      output.text(`Assigned To: ${assignedTo ? assignedTo.name : 'Unassigned'}`);
    }
  }
}

Using Record Values in Calculations

// Get the Sales table
const salesTable = base.getTable('Sales');

// Query sales records for a specific period
const salesRecords = await salesTable.selectRecordsAsync({
  fields: ['Date', 'Product', 'Quantity', 'Unit Price', 'Total']
});

while(salesRecords.hasMoreRecords) {
  await salesRecords.loadMoreRecords()
}

// Perform calculations with record values
let totalSales = 0;
let totalQuantity = 0;
let highestSale = { amount: 0, product: null, date: null };

for (const record of salesRecords.records) {
  const quantity = record.getCellValue('Quantity') || 0;
  const total = record.getCellValue('Total') || 0;
  
  // Update totals
  totalSales += total;
  totalQuantity += quantity;
  
  // Check if this is the highest sale
  if (total > highestSale.amount) {
    highestSale = {
      amount: total,
      product: record.getCellValue('Product'),
      date: record.getCellValue('Date')
    };
  }
}

// Calculate average sale value
const averageSale = salesRecords.records.length > 0 
  ? totalSales / salesRecords.records.length 
  : 0;

// Output the results
output.markdown('# Sales Analysis');
output.text(`Total Sales: $${totalSales.toLocaleString()}`);
output.text(`Total Quantity: ${totalQuantity.toLocaleString()} units`);
output.text(`Average Sale: $${averageSale.toFixed(2)}`);

if (highestSale.product) {
  output.markdown('## Highest Sale');
  output.text(`Product: ${highestSale.product}`);
  output.text(`Amount: $${highestSale.amount.toLocaleString()}`);
  output.text(`Date: ${highestSale.date ? new Date(highestSale.date).toLocaleDateString() : 'Unknown'}`);
}

Complex Record Processing

// Get the Inventory table
const inventoryTable = base.getTable('Inventory');

// Get all inventory records
const inventoryRecords = await inventoryTable.selectRecordsAsync({
  fields: ['Item Name', 'Category', 'Quantity', 'Reorder Level', 'Supplier', 'Price']
});

// Identify items that need to be reordered
const reorderItems = [];
const lowStockItems = [];
const outOfStockItems = [];

for (const record of inventoryRecords.records) {
  const itemName = record.getCellValue('Item Name');
  const quantity = record.getCellValue('Quantity') || 0;
  const reorderLevel = record.getCellValue('Reorder Level') || 0;
  
  // Add to appropriate lists
  if (quantity === 0) {
    outOfStockItems.push({
      id: record.id,
      name: itemName,
      category: record.getCellValue('Category'),
      supplier: record.getCellValue('Supplier'),
      price: record.getCellValue('Price')
    });
  } else if (quantity <= reorderLevel) {
    reorderItems.push({
      id: record.id,
      name: itemName,
      category: record.getCellValue('Category'),
      currentQuantity: quantity,
      reorderLevel: reorderLevel,
      supplier: record.getCellValue('Supplier'),
      price: record.getCellValue('Price')
    });
  } else if (quantity <= reorderLevel * 1.2) {
    // Items at 120% or less of reorder level are considered low stock
    lowStockItems.push({
      id: record.id,
      name: itemName,
      category: record.getCellValue('Category'),
      currentQuantity: quantity,
      reorderLevel: reorderLevel
    });
  }
}

// Group reorder items by supplier
const itemsBySupplier = {};

for (const item of reorderItems) {
  const supplier = item.supplier || 'Unknown Supplier';
  
  if (!itemsBySupplier[supplier]) {
    itemsBySupplier[supplier] = [];
  }
  
  itemsBySupplier[supplier].push(item);
}

// Output the inventory report
output.markdown('# Inventory Report');

if (outOfStockItems.length > 0) {
  output.markdown('## Out of Stock Items');
  output.table(outOfStockItems);
}

if (reorderItems.length > 0) {
  output.markdown('## Items to Reorder');
  output.table(reorderItems);
  
  output.markdown('## Reorder by Supplier');
  for (const [supplier, items] of Object.entries(itemsBySupplier)) {
    output.markdown(`### ${supplier}`);
    
    // Calculate order totals
    const orderTotal = items.reduce((sum, item) => sum + (item.price || 0), 0);
    
    output.table(items);
    output.text(`Order Total: $${orderTotal.toLocaleString()}`);
  }
}

if (lowStockItems.length > 0) {
  output.markdown('## Low Stock Items');
  output.table(lowStockItems);
}

Best Practices

  1. Check for null values - Always handle the possibility of null or undefined field values with default values or conditional logic.

  2. Use appropriate methods - Use getCellValue() when you need the actual value for calculations or logic, and getCellValueAsString() for display purposes.

  3. Handle linked records properly - Remember that linked records from getCellValue() only contain minimal information (id and name). For detailed information, query the linked records from their table.

  4. Field parameter consistency - Prefer using field names (strings) for consistency unless you need special field object functionality.

  5. Error handling - Wrap record operations in try-catch blocks to handle possible errors gracefully.

  6. Update records with care - When updating records, preserve existing values for fields you don't intend to change.