A Chrome extension that turns Google Calendar's ~20-click .ics import into a
two-click flow: click the toolbar icon, click a recently-downloaded .ics
file, and its events are pushed straight into your chosen Google Calendar.
- 📂 Lists
.icsfiles from your downloads, newest first - 🖱️ One click imports every event in the file via the Google Calendar API
- ⚙️ Settings to pick the target calendar and limit the source folder
- 🔁 Handles all-day events, time zones, recurring events (RRULE), and folded lines
- 🧷 Manual file-picker fallback if direct file reading isn't enabled
Chrome extensions are sandboxed and cannot freely scan ~/Downloads. This
extension uses the chrome.downloads API, which lists every file Chrome
itself downloaded — exactly the .ics files you grab from email/web. Files
copied into the folder by other apps won't appear (use Pick a file manually…
for those).
To read a file's contents it fetches file://<path>, which requires the
extension's "Allow access to file URLs" toggle (one-time, step 5 below).
Importing uses the Google Calendar API with OAuth — no fragile clicking through the Calendar UI. That requires a one-time OAuth client (steps 1–4).
- Go to https://console.cloud.google.com/ → create a project (any name).
- APIs & Services → Library → search Google Calendar API → Enable.
- APIs & Services → OAuth consent screen → User type External → Create.
- Fill in the app name and your email where required.
- On Test users → Add users, add the exact Google account you'll sign
in with (in the newer console this is under Audience). Keeping the app in
Testing is fine for personal use — no Google verification needed — but an
account that isn't on this list will be rejected with
403 access_denied.
- Make a copy of
manifest.json.orignamedmanifest.jsonin this folder. (The repo ships only the template; your workingmanifest.jsonis git-ignored so your client ID is never committed.) - Open
chrome://extensions, enable Developer mode (top-right). - Load unpacked → select this folder.
- Copy the extension's ID (a long string of letters under its name).
- APIs & Services → Credentials → Create credentials → OAuth client ID.
- Application type: Chrome Extension.
- Paste the extension ID from step 3 into the Item ID field → Create.
- Copy the Client ID that Google now generates — this is the value you
put in the manifest, and it is not the same as the extension ID.
- ✅ Client ID: digits, a dash, then random chars —
123456789012-a1b2c3d4...apps.googleusercontent.com - ❌ Extension ID: 32 letters, no digits, no dash — e.g.
nbkpnc...lbemi(that's the Item ID you typed in; don't paste it back into the manifest)
- ✅ Client ID: digits, a dash, then random chars —
- In the
manifest.jsonyou created in step 3, replaceREPLACE_WITH_YOUR_CLIENT_ID.apps.googleusercontent.comwith your Client ID. - Back on
chrome://extensions, click the reload ↻ icon on the extension.
Keep this folder where it is — moving it can change the extension ID, which would break the OAuth client mapping. If that happens, repeat step 4 with the new ID.
On chrome://extensions → this extension → Details → turn on
"Allow access to file URLs." (Without it, use the manual file picker.)
- Click the toolbar icon → the ⚙ gear → Settings.
- Click Connect Google Calendar, approve access, and pick your target calendar and optional source folder. Save.
- Done. From now on: click the icon → click an
.icsfile → imported. ✅
| File | Purpose |
|---|---|
manifest.json |
Extension manifest (MV3); holds your OAuth client ID |
popup.html/.css/.js |
The toolbar popup: lists files and triggers imports |
options.html/.css/.js |
Settings: connect account, choose calendar & folder |
lib/ics.js |
RFC 5545 .ics → Calendar API event parser |
lib/gcal.js |
OAuth + Google Calendar API client |
make_icons.py |
Regenerates the PNG icons in icons/ |
"Check the OAuth client ID in manifest.json…" / auth fails instantly
You probably pasted the extension ID instead of the generated Client ID.
They look different — see step 4 above. Go to Credentials → OAuth 2.0 Client
IDs, open your Chrome Extension client, copy the Client ID
(NNNN-xxxx.apps.googleusercontent.com) into manifest.json, and reload the
extension (↻ on chrome://extensions).
Error 403: access_denied
Your account isn't authorized for the app yet. This is the OAuth consent
screen, not your client ID. Fix it:
- APIs & Services → OAuth consent screen (or Audience) → confirm Publishing status: Testing.
- Test users → Add users → add the account you're signing in with → Save.
- Make sure that account matches your Chrome profile's Google account — the consent popup uses whichever account the profile is signed into. If your profile is a personal Gmail, add that, not a different address.
- Retry Connect.
Google Workspace accounts: if your domain (e.g. a
@your-companyaddress) restricts third-party apps, test-user status alone won't be enough — a Workspace admin must allow the app (admin.google.com → Security → API controls), or sign in with a personal Gmail added as a test user instead.
The file list is empty Only files Chrome downloaded appear. Files placed in the folder by other apps won't show — use Pick a file manually… in the popup.
"Couldn't read the file directly"
Turn on "Allow access to file URLs" for the extension
(chrome://extensions → Details), or use the manual picker.
- Events are created fresh on each import; re-importing the same file makes duplicates (the API has no built-in de-dupe here).
- Time zones use the
.icsfile'sTZID; floating times (no zone) are interpreted by Google Calendar in your calendar's default zone. - Custom
VTIMEZONEdefinitions are ignored — standard IANA names work best. - Scopes requested: read your calendar list, and create events. Nothing else.