Google Apps Script

https://developers.google.com/apps-script

Automations

Auto-sort by column

function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  const lastRow = sheet.getLastRow();
  const range = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn()); // Get data starting from row 2 (Assumes row 1 is a header row )
  
      if (sheet.getName() === "Sheet1") {
    // Sort the range by column 6 (F), ascending
    range.sort({ column: 6, ascending: true });
  }
}

Handle Manual Execution

  • If you want to run the script manually for testing, you can modify the code to handle the absence of the event object (e).
 if (!e) {
    // Simulate the event object for manual testing
    e = { source: SpreadsheetApp.getActiveSpreadsheet() };
  }
 
  const sheet = e.source.getActiveSheet();

Create dynamic header dictionary

// Function to create the header dictionary
function getHeaderDict(sheet) {
  const lastRow = sheet.getLastRow();
  const headerRow = 1; // The row containing the headers
 
  // Get the headers from the first row
  const headers = sheet.getRange(headerRow, 1, 1, sheet.getLastColumn()).getValues()[0];
 
  // Create a dictionary (object) of headers with their column indexes
  const headerDict = headers.reduce((acc, header, index) => {
    acc[header] = index + 1; // Add 1 because column indexes are 1-based in Apps Script
    return acc;
  }, {});
 
  Logger.log('Header Dictionary: %s', JSON.stringify(headerDict));
  return headerDict;
}

Auto-sort with dynamic header lookup

function sortData(sheet, headerDict){
  const lastRow = sheet.getLastRow();
  const dateColumnIndex = headerDict["c"];
 
  if (!dateColumnIndex) {
    console.error("The 'Date' column was not found.");
    return;
  }
 
  // Define the data range starting from row 2
  const dataRange = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn());
 
  // Sort the range by the "Date" column, ascending
  dataRange.sort({ column: dateColumnIndex, ascending: true });
}

Add timestamp

// Auto-add timestamp (or clear if rest of row was cleared)
function addTimestamp(sheet, row, headerDict) {
  const dateAddedIndex = headerDict["Date Added"];
  const lastCol = sheet.getLastColumn();
 
  const rowValues = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
  const dateValue = rowValues[dateAddedIndex - 1];
 
  // Check whether rest of row was cleared
  const hasOtherData = rowValues
    .filter((_, index) => index !== (dateAddedIndex - 1))
    .some(cell => cell !== "" && cell !== null);
 
  if (hasOtherData && !dateValue) {
    // Add timestamp if there's other data but no timestamp yet
    const timestamp = new Date();
    sheet.getRange(row, dateAddedIndex).setValue(timestamp);
  } else if (!hasOtherData && dateValue) {
    // Clear timestamp if it's the only value in the row
    sheet.getRange(row, dateAddedIndex).clearContent();
  }
}
 

Run script when sheet is edited

// Function triggered on edit event
function onEdit(e) {
  if (!e) {
    // Simulate the event object for manual testing
    e = { source: SpreadsheetApp.getActiveSpreadsheet() };
  }
 
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== "Sheet1") return; // Only run on "Sheet1"
 
  // Get the header dictionary from the sheet
  const headerDict = getHeaderDict(sheet);
 
  const editedRange = e.range;
  const editedRow = editedRange.getRow();
 
  // Add timestamp if needed
  addTimestamp(sheet, editedRow, headerDict);
 
  // Auto-sort 
  sortData(sheet,headerDict);
}

Starter Template

Todo: add option for Time column

Template Sheets

7/15/25

General Template

Time Tracking Template

  • If the sort column can be renamed (e.g. “Date” “Due Date”), this must be reflected in the SortData() method.

  • Length can be shown in hours (unhide Column I) or minutes

  • Time Tracking Template link | Published