Calrows

Calrows vs writing your own Google Apps Script

Updated 7 October 2026. Written by the developer of Calrows; Apps Script limits link to Google's documentation.

You don't need an add-on to put Google Sheets™ rows on Google Calendar™. Apps Script, built into every Google Sheet, can create, update and read events for free. Here is a script that works, the limits it runs into, and when Calrows is worth installing instead.

The short answer

Write the script if the job is small or one-off, you're comfortable with JavaScript, or you need something Calrows doesn't do, such as recurring events. Use an add-on if the sheet keeps changing, takes minutes to process, needs both directions, or must keep working for people who don't code. Calrows runs on Apps Script too, so Google's limits still apply; the code that handles them is already written.

A minimal Apps Script that works

Make a tab named Events with headers in row 1: A Title, B Start, C End (optional), D Event ID (leave it empty). Start and End must be full date-times, not text or a time on its own. Open Extensions › Apps Script, paste the code, save and click Run. The first run asks you to allow access to your spreadsheets and calendars.

/**
 * Tab "Events", headers in row 1.
 * A: Title   B: Start   C: End (optional)   D: Event ID (leave empty)
 */
function syncRowsToCalendar() {
  var sheet = SpreadsheetApp.getActive().getSheetByName('Events');
  var calendar = CalendarApp.getDefaultCalendar(); // or CalendarApp.getCalendarById('...')
  var rows = sheet.getDataRange().getValues();
  var started = Date.now();

  for (var i = 1; i < rows.length; i++) {           // rows[0] is the header row
    if (Date.now() - started > 5 * 60 * 1000) {      // stop before Google's 6-minute limit
      console.log('Stopped at row ' + (i + 1) + '. Run again to continue.');
      return;
    }
    var title = String(rows[i][0]).trim();
    var start = rows[i][1], end = rows[i][2], id = rows[i][3];
    if (!title || !(start instanceof Date)) continue;  // skip blank rows and text dates
    if (start.getFullYear() < 1900) continue;          // a time with no date reads as 30 Dec 1899
    if (!(end instanceof Date)) end = new Date(start.getTime() + 60 * 60 * 1000); // 1 hour

    if (id) {                                          // sent before: update, never re-create
      var event = calendar.getEventById(id);
      if (!event) continue;                            // no such event in this calendar
      if (event.getTitle() !== title) event.setTitle(title);
      if (event.getStartTime().getTime() !== start.getTime() ||
          event.getEndTime().getTime() !== end.getTime()) {
        event.setTime(start, end);
      }
      continue;
    }

    var created = calendar.createEvent(title, start, end);
    sheet.getRange(i + 1, 4).setValue(created.getId()); // sheet row i + 1, column D
    SpreadsheetApp.flush();                             // write the ID now, not at the end
  }
}

To run it on a schedule, open Triggers (the clock icon) in the editor and add a time-driven trigger for syncRowsToCalendar.

It leaves out all-day events, guests, descriptions, deleting events for deleted rows, pulling Calendar edits back, spotting rows that share an ID after a copy, and reporting which row failed. Any error stops the run there.

The limits a do-it-yourself script runs into

Six minutes per run

Google stops any script after six minutes with "Exceeded maximum execution time". Older posts mention 30 minutes for Workspace, but Google's quotas page now lists 6 minutes per execution for personal and Workspace accounts alike. Each new event is a call to Google Calendar, so a few hundred new rows can run out of time. Continuing automatically means saving your place and scheduling a one-off trigger.

Daily quotas and rate limits

Apps Script can create 5,000 calendar events a day on a personal Google account and 10,000 on Google Workspace, per user. A short-term limit can bite first: rapid calls can fail with "Service invoked too many times in a short time: Calendar. Try Utilities.sleep(1000) between calls." That one-second pause also caps a five-minute run at fewer than 300 rows.

Deleted events don't always look deleted

Deleting an event in Google Calendar marks it as cancelled, and Google's Calendar API reference says its get method always returns cancelled events. In our testing, getEventById() also returned deleted events, while getEvents() for the same time left them out. So the script above can't tell a deleted event from a live one; to catch it, check that getEvents() over the event's times still lists its ID.

Triggers and their daily allowance

Your own trigger can run as often as every minute; add-ons are limited to once an hour. But trigger runs share a daily allowance: 90 minutes on a personal account, 6 hours on Workspace. A five-minute run every hour needs 120 minutes a day, more than a personal account allows, and past the allowance runs fail with "Service using too much computer time for one day". And a scheduled run that overlaps a manual one can create the same events twice unless you add a LockService lock.

Everything else, and keeping it working

All-day events need createAllDayEvent(), whose end date is exclusive, and can land a day off if the script's time zone differs from the spreadsheet's. Then you maintain it. A column inserted in the wrong place, or a cleared column D, can make it create every event again. The platform changes too: Google deprecated the original Rhino runtime, so older scripts must move to V8. Easy for a developer; harder once the developer has moved on.

Calendar to Sheets, and timesheets

The reverse is simpler: calendar.getEvents(start, end) returns a range of events, and one setValues() call writes them out. All-day events are stored as ending the day after they finish, and long ranges can hit the six-minute limit. For a timesheet, skip all-day events and divide end - start by 3,600,000 for hours. Our export guide has a complete script.

In Calrows, Import from Calendar and Timesheet do this from the sidebar; timesheets include rates, amounts, totals and a summary by title. The free plan covers 50 events per run. Pro is unlimited, but these don't continue in a second run, so a range too big for the time limit is cut short and flagged.

Side by side

Your own Apps ScriptCalrows
CostFreeFree: 50 new events a month; imports and timesheets up to 50 events. Pro: $6 for a month or $48 for a year. Team: $190 for a year, up to 10 people. One-time payments, no auto-renewal
SetupAdapt the code to your columnsInstall, pick a calendar, confirm the column mapping it guesses
Running it againNo duplicates only if you store event IDsHidden _eventId column: changed rows update, unchanged rows are skipped
ID column deleted or clearedNext run duplicates every eventRefuses to send
Over six minutes of workStops; run it again or write continuation codePauses at 4.5 minutes; a follow-up run continues from that row
Google rate limitsThe run fails unless you catch the errorStops at that row; the next run picks up the rest
Two runs at onceCan create the same events twiceA lock blocks the second run
Event deleted in CalendargetEventById() may still return itDetected; the row is logged as an error
Row deleted from the sheetIts event stays unless you code deletion"Remove events for deleted rows", after you confirm; only events Calrows created from that sheet
Calendar edits back to the sheetMore code"Pull changes from Calendar into sheet", for linked rows
Calendar to Sheets, timesheetsSeparate scriptsBuilt in, any date range
ScheduleA trigger, as often as every minutePro: hourly, every 6 hours or daily
Recurring events, Meet links, a calendar per rowPossible (createEventSeries(), the advanced Calendar service)Not supported
Record of each runThe Executions page, plus your own loggingSync Log tab: created, updated and failed rows, with reasons
Your dataStays in your Google accountStays in your Google account; only your email address is sent, to check your plan
MaintenanceYouThe developer

When you should just write the script

When an add-on is easier

What Calrows doesn't do

Frequently asked questions

Can Google Apps Script sync Google Sheets with Google Calendar?

Yes. createEvent() turns a row into an event, and storing the ID from getId() in the sheet lets later runs update that event instead of duplicating it. Two-way updates, deletions, all-day events and long sheets take more code.

Why does my Apps Script create duplicate calendar events?

Because createEvent() always makes a new event. Save each event's ID in a column as soon as it is created, and on later runs update the event from getEventById() instead.

How do I fix "Exceeded maximum execution time" when creating calendar events?

Apps Script stops every run after six minutes, on personal and Google Workspace accounts alike. Stop your loop at about five minutes and run again; with IDs stored, finished rows aren't duplicated. Calrows pauses at 4.5 minutes and continues from the same row in a follow-up run.

How many calendar events can Apps Script create per day?

Google's quotas page lists 5,000 a day for a personal Google account and 10,000 for Google Workspace, per user. Creating events in quick succession can fail sooner, with "Service invoked too many times in a short time: Calendar".

When is writing your own script the better choice?

For one-off jobs; when you write code and want what Calrows doesn't do, such as recurring events, Meet links or runs more often than hourly; when add-ons aren't allowed; or when you need more than 50 new events a month with no budget.

See also: three ways to create Google Calendar events from Google Sheets and how two-way sync works.

Install Calrows, free