Script: Import CSV records from a file field to another App

Hi everyone! :rocket:

I’d like to share a script I’ve been using to automate data imports. It’s perfect for scenarios where you receive a CSV file (via form or upload) and need to automatically create multiple records in a target App based on that file.

What this script does:

  1. Triggers from a record that contains a CSV file.
  2. Downloads and parses the CSV (handling quotes and delimiters).
  3. Maps CSV columns to specific field IDs in a target App.
  4. Automatically links the new records to a specific “Exhibitor” or “Reference” name.
  5. Posts a summary comment on the original record when finished.

The Script:

/**
 * TAPE SCRIPT: CSV Auto-Importer
 * Description: Reads an attached CSV and creates records in a target App.
 */

const CONFIG = {
  target_app_id: 12345, // ID of the App where records will be created
  source: {
    file_field_id: 11111,     // Field ID containing the CSV file
    reference_field_id: 22222 // (Optional) Field ID for a reference name (e.g. Exhibitor)
  },
  // Map CSV Columns to Target Field IDs. Use 'null' to skip a column.
  csv_mapping: [
    33331, // Column 0 -> Field ID
    33332, // Column 1 -> Field ID
    33333, // Column 2 -> Field ID
    null,  // Column 3 -> Skipped
    33335  // Column 4 -> Field ID
  ],
  target_reference_field_id: 44444 // Field ID in target App to store the reference name
};

// --- HELPER FUNCTIONS ---

function getFieldValueSafe(record, fieldId) {
    if (!record) return null;
    const target = record.data || record;
    const sFieldId = String(fieldId);

    if (target.fields && typeof target.fields.get === 'function') {
        const f = target.fields.get(sFieldId);
        return f ? f.value : null;
    }
    if (target.fields && Array.isArray(target.fields)) {
        const found = target.fields.find(item => String(item.field_id) === sFieldId || String(item?.field?.field_id) === sFieldId);
        if (found) return found.values !== undefined ? found.values : found.value;
    }
    return target[sFieldId] || null;
}

function parseCSVRow(row) {
  const regex = /("((?:[^"]|"")*)"|[^,;]*)([,;]|$)/g;
  const columns = [];
  let match;
  row = row.replace('\r', ''); 
  while ((match = regex.exec(row)) !== null) {
    if (match.index === regex.lastIndex) { regex.lastIndex++; }
    if (match[1] === undefined && match[2] === undefined) break;
    let value = match[1];
    if (value && value.startsWith('"') && value.endsWith('"')) {
      value = value.slice(1, -1).replace(/""/g, '"');
    }
    columns.push(value);
    if (match[2] === "") break; 
  }
  return columns;
}

// --- MAIN EXECUTION ---

async function runImport() {
  console.log('--- Starting CSV Import ---');

  const currentRecord = tape.Record.current || await tape.Record.get(typeof record_id !== 'undefined' ? record_id : current_record_id);
  
  if (!currentRecord) {
    console.error("⛔ Error: Record not found.");
    return;
  }

  try {
    const fileData = getFieldValueSafe(currentRecord, CONFIG.source.file_field_id);
    const refValue = getFieldValueSafe(currentRecord, CONFIG.source.reference_field_id);

    if (!fileData || fileData.length === 0) {
      console.log("No file found in the specified field.");
      return;
    }

    const fileObj = fileData[0].value || fileData[0];
    const fileUrl = fileObj.download_url || fileObj.link || fileObj.url;
    
    if (!fileUrl) throw new Error("File URL not found.");

    // Resolve Reference Name
    let referenceName = "Not Informed";
    if (refValue) {
        referenceName = Array.isArray(refValue) ? (refValue[0].display_value || refValue[0].value) : (refValue.display_value || String(refValue));
    }

    const response = await http.get(fileUrl);
    let csvText = response.body || response.data;
    const rows = String(csvText).split(/\r?\n/);
    const dataRows = rows.slice(1); // Skips header row

    let createdCount = 0;
    let errorCount = 0;

    for (let i = 0; i < dataRows.length; i++) {
      const row = dataRows[i];
      if (!row.trim()) continue;

      try {
          const columns = parseCSVRow(row);
          const newRecordFields = {};
          
          if (CONFIG.target_reference_field_id) {
            newRecordFields[String(CONFIG.target_reference_field_id)] = referenceName;
          }
          
          CONFIG.csv_mapping.forEach((targetFieldId, index) => {
            if (targetFieldId && columns[index] !== undefined) {
                newRecordFields[String(targetFieldId)] = columns[index].toString().trim().replace(/^"|"$/g, '');
            }
          });

          await tape.Record.create(CONFIG.target_app_id, { fields: newRecordFields });
          createdCount++;
      } catch (rowError) {
          errorCount++;
          console.error(`Error on row ${i + 2}: ${rowError.message}`);
      }
    }

    await tape.Comment.create(currentRecord.id, `✅ **Import Finished**\n- Created: ${createdCount}\n- Errors: ${errorCount}`);
    
  } catch (error) {
    console.error("General Error:", error.message);
  }
}

runImport();

Hope this helps someone! Feel free to ask if you need help with the mapping.

:brazil:

6 Likes