<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: twainprod</title>
    <description>The latest articles on DEV Community by twainprod (@twainprod).</description>
    <link>https://dev.to/twainprod</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4174114%2F0a11c7f3-1fe5-42b9-961a-3c2ec5057682.png</url>
      <title>DEV Community: twainprod</title>
      <link>https://dev.to/twainprod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/twainprod"/>
    <language>en</language>
    <item>
      <title>I Built a Browser-Based Tool to Bulk Delete Google Calendar Events</title>
      <dc:creator>twainprod</dc:creator>
      <pubDate>Sat, 10 Oct 2026 18:57:50 +0000</pubDate>
      <link>https://dev.to/twainprod/i-built-a-browser-based-tool-to-bulk-delete-google-calendar-events-3da9</link>
      <guid>https://dev.to/twainprod/i-built-a-browser-based-tool-to-bulk-delete-google-calendar-events-3da9</guid>
      <description>&lt;p&gt;I ran into a problem that looked trivial until I tried to solve it: delete a lot of Google Calendar events without also deleting the ones I still needed.&lt;/p&gt;

&lt;p&gt;One-by-one deletion is fine for a handful of leftovers. It falls apart when you have a packed morning, an old project calendar, or a week of junk you want gone. Google Calendar is great at adding meetings. It is awkward when you want something closer to "clear this afternoon, except these three."&lt;/p&gt;

&lt;p&gt;I looked for a tool that matched that workflow. I did not find one I liked, so I built &lt;a href="https://gcal-cleaner.com/" rel="noopener noreferrer"&gt;GCal Cleaner&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is the story behind it, plus the technical choices that ended up mattering more than the delete button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrcrrysvz971pqzzrteh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrcrrysvz971pqzzrteh.png" alt="GCal Cleaner Official Site" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The product constraint I cared about
&lt;/h2&gt;

&lt;p&gt;Bulk delete is easy to ship as a dangerous shortcut. The harder problem is confidence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Narrow the blast radius (one calendar, one day, one timeslot).&lt;/li&gt;
&lt;li&gt;Preview what will be affected.&lt;/li&gt;
&lt;li&gt;Let people uncheck exceptions.&lt;/li&gt;
&lt;li&gt;Make recurring behavior explicit before anything is removed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That became the whole product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wipe a timeslot, keep what you uncheck.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing deletes until you confirm. Guests are not emailed (&lt;code&gt;sendUpdates=none&lt;/code&gt;). Recurring events ask whether you mean this day, this and following, or the entire series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a browser app, not a backend
&lt;/h2&gt;

&lt;p&gt;I am a frontend developer, and I wanted something people could open without installing anything. More importantly, I did not want to run a server that stores calendar data.&lt;/p&gt;

&lt;p&gt;GCal Cleaner is a static app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Angular 22&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Static marketing site in front&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Identity Services&lt;/strong&gt; token client for OAuth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar API v3&lt;/strong&gt; called directly with &lt;code&gt;fetch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Hosted on &lt;strong&gt;Netlify&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no GCal Cleaner API in the middle. The browser talks to Google. Access tokens live in &lt;code&gt;localStorage&lt;/code&gt; for about an hour. There is no refresh token; reconnect is an explicit user action. Sign-out revokes the token when possible.&lt;/p&gt;

&lt;p&gt;That architecture is not just a cost decision. For a destructive tool, I want the trust story to be simple: if my server is down, your events are not sitting in my database either.&lt;/p&gt;

&lt;p&gt;Scopes are intentionally limited to what the app needs: event management, calendar list (read), calendar delete for secondary calendars, plus basic profile info for the signed-in UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow is the feature
&lt;/h2&gt;

&lt;p&gt;The UI is deliberately scoped:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in with Google.&lt;/li&gt;
&lt;li&gt;Pick a writable calendar.&lt;/li&gt;
&lt;li&gt;Pick a day and a timeslot (full day, morning, afternoon, evening, or custom).&lt;/li&gt;
&lt;li&gt;Load events that overlap that slot.&lt;/li&gt;
&lt;li&gt;Uncheck anything you want to keep.&lt;/li&gt;
&lt;li&gt;Choose recurring behavior if needed.&lt;/li&gt;
&lt;li&gt;Confirm, then delete with progress, pause, and stop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77ubpjyurbb2q6u3jtmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77ubpjyurbb2q6u3jtmk.png" alt="GCal Cleaner App" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few details that make this feel safer in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In-slot events start checked. Selection is tracked as an "unchecked" set, so the default action is "clean the slot," not "manually select everything."&lt;/li&gt;
&lt;li&gt;Events outside the slot stay visible but cannot be selected. That keeps context without widening the blast radius.&lt;/li&gt;
&lt;li&gt;All-day events only enter the selection when the slot is the full day.&lt;/li&gt;
&lt;li&gt;The confirm modal shows titles before the run starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same pattern you want in any bulk UI: select, preview, exclude, then mutate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part: recurring events
&lt;/h2&gt;

&lt;p&gt;Listing a day with &lt;code&gt;singleEvents=true&lt;/code&gt; is the easy half. You get expanded occurrences, which is what humans expect in a day view.&lt;/p&gt;

&lt;p&gt;Deletion is harder, because "delete this recurring event" can mean three different API operations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What the app does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;This day only&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DELETE&lt;/code&gt; the occurrence id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entire series&lt;/td&gt;
&lt;td&gt;Deduplicate by &lt;code&gt;recurringEventId&lt;/code&gt;, then &lt;code&gt;DELETE&lt;/code&gt; the master once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;This and following&lt;/td&gt;
&lt;td&gt;Fetch the master, then &lt;code&gt;PATCH&lt;/code&gt; its &lt;code&gt;RRULE&lt;/code&gt; with an &lt;code&gt;UNTIL&lt;/code&gt; just before the selected occurrence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"This and following" was the sharp edge. Google does not give you a single "delete from here forward" endpoint that matches Calendar's UI perfectly, so the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds the earliest selected occurrence in each series.&lt;/li&gt;
&lt;li&gt;Loads the master event.&lt;/li&gt;
&lt;li&gt;Rewrites recurrence: strip existing &lt;code&gt;UNTIL&lt;/code&gt; / &lt;code&gt;COUNT&lt;/code&gt;, set a new &lt;code&gt;UNTIL&lt;/code&gt; one second earlier (or one day earlier for all-day events).&lt;/li&gt;
&lt;li&gt;Falls back to deleting the instance (or the whole master) when the master is missing, has no &lt;code&gt;RRULE&lt;/code&gt;, or the computed &lt;code&gt;UNTIL&lt;/code&gt; would land before the series start.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Default mode is &lt;strong&gt;this day only&lt;/strong&gt;. That felt like the least surprising default for a cleanup tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rate limits, pauses, and hour-long tokens
&lt;/h2&gt;

&lt;p&gt;Deleting hundreds of events is where a toy script dies and a real tool shows up.&lt;/p&gt;

&lt;p&gt;The delete runner uses a small burst queue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concurrency of 4 deletes (2 for master reads)&lt;/li&gt;
&lt;li&gt;short stagger inside a burst&lt;/li&gt;
&lt;li&gt;gap between bursts&lt;/li&gt;
&lt;li&gt;retries with exponential backoff, jitter, and &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;404/410 treated as success ("already gone")&lt;/li&gt;
&lt;li&gt;mid-run 401 pauses the job and asks you to reconnect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pause and stop are first-class. Pause aborts in-flight fetches and requeues unfinished items; stop cancels the run. That mattered once I hit long cleanups against Google's quotas and one-hour access tokens.&lt;/p&gt;

&lt;p&gt;There is also a secondary-calendar wipe path for owners: try deleting the calendar, and if Google says it is too large, list event ids, burst-delete them, then try the calendar delete again. That was not in the original sketch. Real calendars forced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this taught me about bulk UIs
&lt;/h2&gt;

&lt;p&gt;The delete call is boring. The product work is everything around it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;constrain the domain before offering power&lt;/li&gt;
&lt;li&gt;show the affected set clearly&lt;/li&gt;
&lt;li&gt;make exceptions cheap&lt;/li&gt;
&lt;li&gt;name irreversible modes in plain language&lt;/li&gt;
&lt;li&gt;expect auth expiry and rate limits during long operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those ideas transfer to admin panels, file cleaners, CMS bulk actions, and anywhere users modify many records at once. Speed helps. Predictability is what makes people willing to click confirm.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still want to learn
&lt;/h2&gt;

&lt;p&gt;I am still iterating on GCal Cleaner, and feedback from people with messy calendars is more useful than another feature idea invented in isolation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What makes you hesitate before a bulk calendar delete?&lt;/li&gt;
&lt;li&gt;Do you think in days, timeslots, keywords, or some mix?&lt;/li&gt;
&lt;li&gt;Which recurring behavior needs the clearest warning copy?&lt;/li&gt;
&lt;li&gt;What would make you trust a browser tool with a busy work calendar?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ever wanted a "delete this afternoon" control that Google does not ship, try it at &lt;a href="https://gcal-cleaner.com/" rel="noopener noreferrer"&gt;https://gcal-cleaner.com/&lt;/a&gt;. It is free right now.&lt;/p&gt;

&lt;p&gt;I built it for my own cleanup pain. I would love to hear what works, what feels scary, and what you would change in the workflow.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>buildinpublic</category>
      <category>google</category>
    </item>
  </channel>
</rss>
