Skip to main content

Inspired by this discussion, I wrote a script for the Scripting block that lets you instantly start and stop a virtual timer on any record, provided that the table is set up to work with this script. Aside from adding the script into a Scripting block, setup only requires three new fields:

  1. A single line text field, which will store the script’s timing data (used internally; may be hidden)
  2. A duration field, where the script will store the tracked time
  3. A button field, which must be set to trigger the Scripting block that contains the script. I suggest labeling this button “Start/Stop,” for reasons that you’ll see below.

Use: Click the button once to start the “timer.” Click it again to stop it and see the duration. Need to continue timing on that same record? Click the button again to restart. The next click after that will tally the elapsed time from both timing “sessions” and put the total in the duration field. Start and stop the timer as much as you need.

How it works: Each time you click the button, the script adds the current time (converted to a number) to an array, and stores that array in the single line text field. An time that a button click results in an even number of values in the array, the script calculates the difference between each pair of values, and stores the resulting duration in the duration field.

The benefit of this timer system is that you can have multiple records all “timing” simultaneously, and all using the same Scripting block. Using Airtable’s timer block, you first have to pick a record for the timer, and you need one timer block per record that you wish to time.

One optional feature in the script is to clear the duration field while the timer is “active.” This is one possible way to avoid confusion when looking at a record and wondering whether the timer is active or not. :slightly_smiling_face: It’s not exactly a status indicator, but it’s a first start. I’ve got some other ideas that I plan on adding to later updates, including optional status indicators (text/emojis), and an alternate method of starting/stopping the timer using automation so that the blocks sidebar doesn’t need to be involved.

Here’s the script.

Justin, Do you have the two scripts mentioned above you can share for creating this via an automation?


Nicole,

Here is what I’ve done for my automation to work.

// ------------------------ START CONFIGURATION ------------------------//

let dataField = "Timer Data";
let durationField = "Duration";
let clearWhileTiming = true;
let inputConfig = input.config();
let recordID = inputConfig.recordID;

// ------------------------- END CONFIGURATION -------------------------//

// Get the record
let mainTable = base.getTable("yourTable"); // Load the main table - change "yourTable" according to your base
let mainQueryResult = await mainTable.selectRecordsAsync(); // Get all records in the mainTable
let record = mainQueryResult.getRecord(`${inputConfig.recordID}`); // Find the updated record by using the ID from the input
let timerData = record.getCellValue(dataField);
let values = timerData === null ? [] : JSON.parse(timerData);
let toStore = {};

And on line 74, change table to mainTable


@Nicole_Merwin ,

Did you add input variables on left hand side?


Justin, Do you have the two scripts mentioned above you can share for creating this via an automation?


Sorry, @Nicole_Merwin , but I never got back to the automation version. I might revisit it eventually, but it’s impossible to say when that will be.


@Justin_Barrett firstly this script is amazing. I was wondering if you had looked at this again? I am currently building out in interfaces and having this run via automation with a button trigger would be a game changer.


@Justin_Barrett firstly this script is amazing. I was wondering if you had looked at this again? I am currently building out in interfaces and having this run via automation with a button trigger would be a game changer.


Welcome to the community, @Operations_ACM! :grinning_face_with_big_eyes: Unfortunately finding energy for personal projects has been extremely difficult ever since I went from full-time consultant to full-time employee earlier this year. Now I’m working in Airtable and writing code all day, and as much as I enjoy it, it’s tough to do more of the same after hours. It’s still on my list of things to tackle, but I honestly have no idea when I’ll get to it. Sorry.


Welcome to the community, @Operations_ACM! :grinning_face_with_big_eyes: Unfortunately finding energy for personal projects has been extremely difficult ever since I went from full-time consultant to full-time employee earlier this year. Now I’m working in Airtable and writing code all day, and as much as I enjoy it, it’s tough to do more of the same after hours. It’s still on my list of things to tackle, but I honestly have no idea when I’ll get to it. Sorry.


Hey Justin! Thanks for the warm welcome :star_struck:
No worries, I totally get it & hopefully you manage some inspiration to in the near future. Good luck with your new role and take care :slightly_smiling_face:


Unfortunately not. The way I’m tracking time has no connection to the Time Tracker block, and there’s no way I can interface with that block to stop a timer that has been started with it.

However, that does give me an idea for an update to the script, which would allow you to use either system on the same record and keep everything in sync. The only caveat is that it would only work provided that you start and stop the timer with the same system each time. For example, you could start and stop the timer with my script, then start and stop it with the Time Tracker block, then go back to my script for the next run, and everything would work. What wouldn’t work is to start with one and stop with the other. That’s not technically possible.


Thanks Justin for the original script. I had Claude help me modify it to create a time log in another table. Hope this is useful to anyone.

/**
 * Title: Enhanced Record Timer with Detailed Logging
 * Version: 2.3
 * License: MIT
 * Original Author: Justin Barrett
 * Modified by: Claude (Anthropic)
 * 
 * Description: An enhanced version of the Record Timer script. This script creates
 * detailed time logs in a separate table, tracking assignments, status, and linking
 * to the original record. It avoids creating entries with zero duration and always
 * creates a new log entry for each timing session.
 */

// ------------------------ START CONFIGURATION ------------------------//

let dataField = "Timer Data";
let durationField = "Duration";
let assignedToField = "Assigned to:";
let statusField = "Status";
let orderField = "Order"; // Add this line, assuming you have an "Order" field

let timeLogsTableName = "Time Logs";
let timeLogsFields = {
    recordId: "Record ID",
    assignedPerson: "Assigned Person",
    duration: "Duration",
    timestamp: "Timestamp",
    recordLink: "Record Link",
    status: "Status"
};

// ------------------------- END CONFIGURATION -------------------------//

// Get the tables
let ordersTable = base.getTable(cursor.activeTableId);
let timeLogsTable = base.getTable(timeLogsTableName);

// Get the record
let record = await input.recordAsync("Choose a record", ordersTable);
let recordId = record.id;
let timerData = record.getCellValue(dataField);
let assignedTo = record.getCellValueAsString(assignedToField);
let status = record.getCellValueAsString(statusField);
let orderInfo = record.getCellValueAsString(orderField) || "No order specified"; // Add this line
let values = timerData === null ? [] : JSON.parse(timerData);

// Get the current time
let currentTime = new Date();
let currentTimestamp = currentTime.getTime();
values.push(currentTimestamp);

// Calculate total duration
let totalDuration = 0;
for (let i = 0; i < values.length; i += 2) {
    if (+ 1 < values.length) {
        totalDuration += values[+ 1] - values[i];
    } else {
        totalDuration += currentTimestamp - values[i];
    }
}
totalDuration = Math.round(totalDuration / 1000); // Convert to seconds

// Calculate duration of the current session
let currentSessionDuration = 0;
if (values.length % 2 === 0) {
    currentSessionDuration = Math.round((currentTimestamp - values[values.length - 2]) / 1000); // in seconds
}

// Only proceed with time log creation if there is a duration
if (currentSessionDuration > 0) {
    // Prepare data for the time log
    let timeLogData = {
        [timeLogsFields.recordId]: recordId,
        [timeLogsFields.assignedPerson]: assignedTo,
        [timeLogsFields.duration]: currentSessionDuration,
        [timeLogsFields.timestamp]: currentTime,
        [timeLogsFields.recordLink]: [{id: recordId}],
        [timeLogsFields.status]: status
    };

    // Always create a new log entry
    await timeLogsTable.createRecordAsync(timeLogData);
}

// Update the original record
await ordersTable.updateRecordAsync(record, {
    [dataField]: JSON.stringify(values),
    [durationField]: totalDuration
});

output.markdown(`# Timer ${values.length % 2 === 0 ? 'Stopped' : 'Started'}

**Order:** ${orderInfo}
**Record ID:** ${recordId}
**Assigned To:** ${assignedTo}
**Status:** ${status}
**Total Duration:** ${totalDuration} seconds
**Current Session Duration:** ${currentSessionDuration} seconds
**Timestamp:** ${currentTime.toISOString()}
${currentSessionDuration === 0 ? '\n**Note:** No time log entry was created due to zero duration.' : ''}
`);