Skip to content

Instantly share code, notes, and snippets.

@FrancoStino
Last active January 26, 2026 08:21
Show Gist options
  • Select an option

  • Save FrancoStino/97942f4b85d5716df4808ec20f9f4625 to your computer and use it in GitHub Desktop.

Select an option

Save FrancoStino/97942f4b85d5716df4808ec20f9f4625 to your computer and use it in GitHub Desktop.
Todoist to Google Calendar Public Events

Todoist to Google Calendar Public Events

Description

This script automatically sets all events created by Todoist on Google Calendar to public visibility using sync tokens for efficient incremental updates. It automatically finds your Todoist calendar and processes only new or modified events after the first run. Perfect for when the Todoist–Google Calendar integration creates events as private by default.

Features

  • ✅ Automatically finds the Todoist calendar by name
  • ✅ Uses Google Calendar API sync tokens to process only new or modified events
  • ✅ Sets the visibility of private events to PUBLIC
  • ✅ Efficient: after the first run, only changed events are processed (no date ranges needed)
  • ✅ Works with events scheduled at any date in the future or past
  • ✅ Can run periodically via triggers (every 10–15 minutes)

Prerequisites

  • Google account with access to the calendar connected to Todoist
  • A calendar named "Todoist" (or update the script with your calendar name)
  • Google Calendar API advanced service enabled in Apps Script
  • Permission to modify events on the target calendar

Installation

  1. Go to script.google.com and create a new project
  2. Click on Services (+ icon) and add Google Calendar API
  3. Paste the code into the main script file
  4. If your Todoist calendar has a different name, update cal.summary === 'Todoist' with the correct name
  5. Save the project (Todoist to Google Calendar Public Events)
  6. Run updateTodoistEvents() once to authorize the script

Code

function updateTodoistEvents() {
  try {
    // List all calendars with error handling
    const calendars = Calendar.CalendarList.list();
    
    // Check if we got a valid response
    if (!calendars || !calendars.items) {
      Logger.log('No calendars found or empty response');
      return;
    }
    
    // Find the Todoist calendar ID
    let todoistId = null;
    calendars.items.forEach(cal => {
      Logger.log('Found calendar: ' + cal.summary); // Debug log
      if (cal.summary === 'Todoist') {
        todoistId = cal.id;
      }
    });
    
    if (!todoistId) {
      Logger.log('Todoist calendar not found in available calendars');
      return;
    }
    
    Logger.log('Processing Todoist calendar: ' + todoistId);
    
    const props = PropertiesService.getUserProperties();
    let options = { maxResults: 250 };
    
    // First run: get last 30 days, then only changes
    if (!props.getProperty('syncToken')) {
      const past = new Date();
      past.setDate(past.getDate() - 30);
      options.timeMin = past.toISOString();
    } else {
      options.syncToken = props.getProperty('syncToken');
    }
    
    const events = Calendar.Events.list(todoistId, options);
    
    if (events.items && events.items.length > 0) {
      Logger.log('Processing ' + events.items.length + ' events');
      
      events.items.forEach(event => {
        if (event.status !== 'cancelled' && event.visibility === 'private') {
          try {
            Calendar.Events.patch({visibility: 'public'}, todoistId, event.id);
            Logger.log('Updated event: ' + event.summary);
          } catch (e) {
            Logger.log('Error updating event ' + event.id + ': ' + e.message);
          }
        }
      });
    } else {
      Logger.log('No events found to update');
    }
    
    if (events.nextSyncToken) {
      props.setProperty('syncToken', events.nextSyncToken);
    }
    
  } catch (e) {
    Logger.log('Error: ' + e.message);
    Logger.log('Stack: ' + e.stack);
    
    // If sync token expired, clear it for next run
    if (e.message.includes('Sync token') || e.message.includes('sync token')) {
      PropertiesService.getUserProperties().deleteProperty('syncToken');
      Logger.log('Sync token cleared, will do full sync on next run');
    }
  }
}

How It Works

First execution:

  • Automatically finds your Todoist calendar by name
  • Processes events from the last 30 days
  • Sets all private events to public
  • Saves a sync token for future runs

Subsequent executions:

  • Uses the sync token to retrieve only events created or modified since the last run
  • No date ranges needed - processes only changes
  • Updates private events to public visibility
  • Saves the new sync token

Any event created by Todoist (regardless of when it's scheduled) will be detected and updated within minutes.

Setting Up Triggers

  1. In Apps Script project, open Triggers section
  2. Add new trigger:
    • Function: updateTodoistEvents
    • Event source: Time-driven
    • Frequency: Every 10–15 minutes
  3. Save and authorize

Troubleshooting

Error Solution
"Todoist calendar not found" Verify calendar name in Google Calendar, update cal.summary === 'Todoist'
"API call failed" Enable Google Calendar API in Services
Sync token errors Script auto-resets invalid tokens
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment