Import Google Calendar events as billable time entries into ClickUp timesheets.
Copy the example config and fill in your real client ID:
cp config.json.example config.jsonEdit config.json:
{
"google_client_id": "787917123062-xxxx.apps.googleusercontent.com"
}Your client ID is in the Curotec Google Cloud Console: APIs & Services → Credentials
Under OAuth 2.0 Client IDs, find the entry for this extension and copy the Client ID.
It will look like 787917123062-xxxx.apps.googleusercontent.com.
When creating or editing the OAuth client, you need to add the extension's Chrome ID under Authorized JavaScript origins and Authorized redirect URIs.
Use bdkpjnahpplacaegbglhoilpcpamnkcg as the extension ID for the initial load.
This is the extension ID used during development. After loading the extension in Chrome
for the first time, check chrome://extensions for its actual assigned ID and update
the OAuth client with that value if it differs.
config.jsonis gitignored and will never be committed.manifest.jsononly contains a{{GOOGLE_CLIENT_ID}}placeholder.
node build.jsThis injects your client ID into manifest.json and copies all files to dist/.
If you have archiver installed (npm install archiver), it also produces
dist/gcal-clickup-importer.zip.
- Go to
chrome://extensions - Enable Developer mode
- Click Load unpacked and select the
dist/folder — or drag in the zip file if you built one
Open ⚙️ Settings inside the extension and fill in:
- ClickUp API Token — from ClickUp → Settings → Apps → API Token (starts with
pk_) - ClickUp Team ID — the number in your ClickUp URL, e.g.
9017610002
- Click the extension icon
- Select a date and click Load Events
- Events are cross-checked against existing ClickUp entries:
[existing]— same ticket already logged for that time, unchecked by default[conflict]— different ticket but overlapping time, unchecked by default
- Star (☆) tickets you use frequently to pin them to the top of the dropdown
- Adjust billable checkboxes as needed
- Click Import Selected
To update the extension, edit source files and re-run node build.js,
then reload the extension at chrome://extensions.
- Skip list is now pre-populated on fresh install (Lunch, Break, OOO, Out of office, PTO)
- Fixed skip list intermittently not applying: migrated skipList storage from chrome.storage.sync to chrome.storage.local so all read paths are consistent
- Session persistence: loaded events are saved to storage and restored when the popup reopens
- Added Cancel button to discard the session and return to the date picker
- Added margin-top to .timer-controls for visual separation from ticket name label
- Fixed timer icon alignment: moved ticket name label outside the combo div so it no longer inflates the row height
- Added
ticket-name-label:empty { display:none }to prevent empty labels from taking up space
- Update checker now points to GitHub repo (alberto-curotec/gCal-2-ClickUp)
- Fixed vertical alignment of checkmark and star icons in the timer ticket row
- Removed leading dot from selected count label
- Matched font size of selected count and time sum to event count
- Fixed ticket name label:
applyTicketValidationnow creates the label element AND sets its text — previous versions had a broken reference to an undefinednameLabelvariable
- Fixed ticket name label not appearing in calendar event rows — switched to insertAdjacentElement for reliable placement after the ticket input row
- Ticket name label font size increased to 12px
- Fixed ticket name label placement in calendar event rows — now appears below the ticket input row rather than inside the narrow combo container
- Ticket name now shown below the ticket input after validation in:
- Calendar event rows
- Timetracker ticket input
- Timetracker confirmation panel
- Clears when ticket ID changes or is invalid
- Fixed Recurrent Events Rules not pre-filling ticket IDs in popup — the getMatchingRule call was missing from renderEvents after a previous refactor
- Renamed "Event Rules" section to "Recurrent Events Rules"
- Fixed ticket names not showing in Event Rules dropdown for new rule rows
- Fixed popup horizontal scroll caused by wide dropdown (reduced to 270px)
- Fixed popup vertical scroll caused by dropdown pushing layout instead of overlaying (z-index and overflow fixes)
- Fixed ticket suggestions dropdown in Event Rules — was rendering as a plain list instead of a positioned overlay; fixed CSS scoping and z-index
- Event Rules ticket input now shows favorite and frequent ticket suggestions dropdown on focus, same as the popup ticket inputs
- Fixed "Load upcoming events" button not responding — added proper error handling for chrome.runtime.lastError and undefined response cases
- Added Event Rules in ⚙️ Settings:
- Load upcoming calendar events (next 2 weeks) as rule suggestions
- Click a suggestion to create a rule, or add manually with title + time
- Each rule has ticket ID (validated), billable toggle, tag dropdown
- Matching: title contains (case-insensitive) + optional time (HH:MM)
- Priority: title+time match > title only > time only
- On event load, matching events auto-fill ticket, billable and tag
- Fixed ticket suggestions dropdown width (min 300px, no longer clipped to the narrow ticket input width)
- Ticket suggestions dropdown now shows all options without a scrollbar
- Tag Manager checklist height doubled (260px → 520px) to show more tags
- Ticket suggestions dropdown now shows up to 8 frequent tickets (was 5), plus up to 3 favorites, for a total of 11 suggestions
- Tag Manager moved above Favorite Tickets in settings
- Tag Manager checklist now supports drag-and-drop reordering — order is preserved in enabledTags and restored on next open
- Favorite tickets now have an optional tag dropdown in settings — pre-fills the tag dropdown in the popup when that ticket is selected
- Added Tag Manager in ⚙️ Settings:
- Fetches all workspace tags from ClickUp on first open
- Checklist lets you select which tags appear in the dropdown
- Refresh button to reload tags from ClickUp
- Select all / Select none shortcuts
- Tag dropdowns in popup only show enabled tags
- Added optional tag selector to time entries:
- Tags fetched from ClickUp workspace time entry tags API (cached 10 min)
- Tag dropdown appears to the right of the ticket input after validation
- Ticket input narrowed to half-width to make room for the tag dropdown
- Tag preference saved per ticket and pre-fills on next use
- Tag also available in timetracker confirmation panel
- Tag sent to ClickUp when logging time entries
- Added selected event count next to event count (e.g. "11 events found · 3 selected")
- Fixed auto-stop losing tracked time — confirm panel state is now saved to storage before the timer is cleared, so time is never lost on auto-stop
- Notification Stop button also saves confirm state correctly
- Starting a new timer is blocked while a confirmation panel is pending
- Notification message updated to clarify time is saved
- Added elapsed time badge on the extension icon:
- Green badge shows elapsed minutes/hours when timer is running (e.g. 14m, 1h)
- Orange badge when timer is paused
- Badge clears when timer is stopped or auto-stopped
- Updates every 30 seconds, restores correctly after browser restart
- Fixed pause button not appearing when timer starts
- Added Pause/Resume to the timetracker:
- ⏸ Pause button (orange) appears next to ⏹ Stop when timer is running
- ▶ Resume button (green) replaces Pause when timer is paused
- Elapsed time is preserved correctly across pause/resume cycles
- Paused state persists across popup close/reopen
- Chrome notification fires every 5 minutes while paused as a reminder
- Added automatic update checker — popup shows a banner when a newer version is available on Bitbucket, with a direct link to the repository
- Improved setup documentation:
- OAuth Client ID section now links directly to the Curotec Google Cloud Console credentials page
- Added instructions for using bdkpjnahpplacaegbglhoilpcpamnkcg as the initial extension ID when setting up OAuth
- ClickUp Team ID now pre-filled with the Curotec workspace ID (9017610002) so new users don't need to look it up
- Timetracker now detects ticket ID from the ClickUp DOM when the URL doesn't contain it (e.g. inbox view) — reads from the task label button using the data-test="task-view-task-label__taskid-button" selector, with a fallback to scanning the page text
- Added billable preference per ticket:
- Favorites: each favorite row in settings now has a Billable toggle (checked by default), saved immediately on change
- Frequent tickets: billable status is saved every time a time entry is imported, so the last-used status is remembered
- Priority: favorite setting > last-used > default (billable)
- Pre-fills automatically in event row billable checkboxes on load and when a ticket is selected from the dropdown
- Pre-fills in timetracker confirmation panel when timer stops or ticket ID is changed
- Removed temporary forced debug logging — debug mode back to settings toggle
- Fixed applyTicketValidation not applying isProtected check — previous edits were not persisting to file; verified and reapplied correctly
- Forced debug logging always on temporarily to diagnose [existing] checkbox issue
- Fixed applyTicketValidation re-enabling protected checkboxes on valid tickets
- Fixed [existing] rows being re-enabled after ticket validation — runValidation empty-id path was unconditionally re-enabling all checkboxes including protected rows
- Fixed [existing] events getting re-checked after ticket validation — applyTicketValidation now skips re-enabling checkboxes on rows that have a status-warning or status-danger class
- Fixed [existing] checkbox not being disabled — HTML attribute alone was unreliable; now also sets disabled imperatively via JS after the element is added to the DOM
- Added debug logging to status detection loop for diagnosing [existing] checkbox issue
- Fixed [existing] events not being disabled — checkbox attribute was not being applied correctly in HTML string; now uses explicit checked="checked" and disabled="disabled" forms
- Fixed bug where [existing] and [conflict] events were still checked and enabled — they are now unchecked and disabled to prevent duplicate entries
- Added total selected time sum displayed next to the event count (green, updates dynamically as checkboxes are toggled)
- Added ticket ID validation to the timetracker ticket input:
- Pre-filled/auto-detected tickets validated on load
- Typing validates after 600ms debounce
- Dropdown selections validate immediately
- Same ⏳/✔/✖ icons as calendar rows and confirm panel
- Added ticket ID validation against ClickUp API for both calendar events and
the timetracker confirmation panel:
- Calendar events: pre-filled ticket IDs validated on load (300ms stagger), manually entered IDs validated on demand (600ms debounce)
- ⏳ shown while checking, ✔ yellow if valid, ✖ red if not found
- Invalid rows are grayed out, checkbox unchecked and disabled
- Row restores to normal when a valid ticket ID is entered
- Timetracker: ticket validated on blur/debounce, Log Time stays disabled until both ticket is valid and description is filled
- Timetracker confirmation panel improvements:
- Added mandatory Description field (3 lines) — Log Time stays disabled until filled
- Description is sent as the ClickUp time entry description (not the ticket ID)
- Removed +/- duration buttons — rounded duration shown as plain text
- Raw tracked time shown in top right corner of the panel for reference
- Timer section hides when calendar events are loaded and reappears after import completes or when Load Events is clicked again
- Full housekeeping rewrite of popup.js:
- Removed duplicate cleanTitle() function
- Fixed ticket ID detection to use rawTitle (pre-clean) not cleaned title
- Removed dead filter in getFrequentTickets
- Unified dropdown builder into single buildDropdown() used by both event list and timer combos
- Removed all leftover debug code and dead blocks from previous attempts
- Consistent wireCombo() with onSelect callback throughout
- Fixed star not highlighting on first dropdown selection — complete rewrite of wireTimerCombo confirmed in place with working DOM node handlers
- Complete rewrite of wireTimerCombo — uses DOM nodes with direct per-item mousedown handlers, synchronous dbg() logging
- Fixed dbg() helper to cache debug mode synchronously so logs work inside event handlers (previously async storage read caused logs to be missed)
- Added global mousedown tracker and dropdown open log for debugging
- Rewrote dropdown item rendering to use createElement and attach mousedown handlers directly to each item instead of innerHTML + event delegation
- Fixed dropdown click not firing — switched to mousedown+mouseup pair with preventDefault to capture selection before blur closes the dropdown
- Replaced event-based dropdown selection with polling + click approach to reliably detect value changes regardless of blur/mousedown race conditions
- Fixed star sync — blur was calling syncStar with empty value before mousedown completed; onSelect now only fires on explicit input changes and dropdown selection
- Switched dropdown click handler to document-level event delegation to fix star sync on first selection
- Restored debug logs for dropdown and star sync, gated behind the Debug Mode setting in options — enable it to see [GCal→ClickUp] logs in the console
- Fixed star not highlighting on dropdown selection — root cause was that innerHTML re-renders on each keystroke were destroying the mousedown listener; switched to event delegation on the parent container which survives re-renders
- Fixed star not highlighting on dropdown selection — root cause was that innerHTML re-renders on each keystroke were destroying the mousedown listener; switched to event delegation on the parent container which survives re-renders
- Fixed star highlight on dropdown selection using preventDefault on mousedown to stop the input blur from firing before the value is set
- Fixed star not highlighting on first dropdown selection — blur event was firing before input.value was set, causing syncStar to read an empty value; onSelect now fires synchronously in mousedown before blur can clear the input
- Fixed star not highlighting on first dropdown selection — syncStar now reads directly from chrome.storage.local instead of the async getFrequentTickets(), eliminating the race condition on first open
- Fixed timer star not highlighting after selecting a favorite from the dropdown
- Star now correctly turns yellow on dropdown selection, typing, and paste
- Timer star icon now reliably highlights yellow when the selected ticket is in the favorites list — fixed timing issue where star was checked before the dropdown wiring completed, and added sync when selecting from dropdown
- Timer confirmation panel now persists across popup close/reopen — ticket ID, duration and billable state are saved to storage and restored when you reopen the popup, so tracked time is never lost until you explicitly Log or Discard
- Favorite ticket ID input is now shorter (12 char width) with maxlength enforced
- Add favorite from settings now validates the ticket against ClickUp API:
- Yellow ✔ and task name shown when ticket exists
- Red ✖ and error message when ticket not found
- Add button stays disabled until validation passes
- Validation triggers automatically 600ms after typing stops, or on blur
- Task name is fetched from ClickUp and stored automatically (read-only)
- Timer star icon now correctly highlights on load when the auto-detected or restored ticket ID is already a favorite
- Added Ad-hoc Timer below the date picker:
- Auto-detects ticket ID from the active ClickUp tab URL
- Editable ticket input with favorites + frequent ticket dropdown and star button
- ▶ Start / ⏹ Stop button with live HH:MM:SS counter
- Timer persists across popup close/reopen via chrome.storage.local
- At 1 hour: Chrome notification fires with Continue / Stop buttons
- No response within 1 minute: timer auto-stops
- On stop: confirmation panel with editable ticket ID, duration (rounded up to nearest 5 min, adjustable in 5 min steps), billable checkbox (default on), and Log Time / Discard buttons
- Updated extension icon with revised version
- Added extension icon (ClickUp + Google Calendar combo) in all required sizes: 16×16, 32×32, 48×48, 128×128
- Icon appears in the Chrome toolbar, extensions page, and Chrome Web Store listing
- Every event now shows an editable ticket ID input, pre-filled when a ticket ID is detected from the event title
- Ticket ID badge removed from title display (it now lives solely in the input)
- Star button reads the current input value at click time, so you can favorite a ticket after editing it
- Favorite tickets can now be fully managed from ⚙️ Settings:
- Add a ticket by ID and optional title directly on the settings page
- Edit a ticket's title inline with a ✓ save button
- Remove any favorite with the ✕ button
- Previously favorites could only be added via the popup star and removed from settings
- Ticket ID is now stripped from the event title both in the popup display and when sent to ClickUp as the time entry description (e.g. "Fix bug CTK-123" becomes "Fix bug")
- Added Favorite Tickets — star (☆) any ticket with a detected ID to pin it to the top of the dropdown; stars turn yellow when active
- Favorites list in ⚙️ Settings shows ticket ID + name with a remove (✕) button
- Dropdown now shows favorites first (up to 3), then frequent tickets, deduped
- Ticket ID suggestions dropdown now shows task name alongside ID
(e.g.
CTK-1640 – Gen Admin) - Task names fetched from ClickUp API on first use and cached locally
- Added Reset ticket history button in ⚙️ Settings to clear frequency data and start fresh
- Ticket ID input replaced with a combo dropdown showing the 5 most-used tickets from the last 30 days (rolling window, auto-pruned)
- Ticket use frequency stored in
chrome.storage.localand updated on every successful import
- Import Google Calendar events as ClickUp time entries via the ClickUp REST API (no DOM scraping)
- Cross-check calendar events against existing ClickUp entries for the selected day:
[existing]badge for already-logged entries,[conflict]badge for time overlaps - Both badges uncheck the event by default to prevent duplicates
- Billable checkbox per event (checked by default)
- ClickUp user timezone detected and displayed below the event list
- Skip List in settings — keywords that auto-deselect matching events
- Debug Mode toggle in settings — writes detailed logs to the browser console
- OAuth Client ID stored in
config.json(gitignored) and injected at build time viabuild.js, keepingmanifest.jsonsafe to commit - ClickUp API Token and Team ID stored in
chrome.storage.localvia ⚙️ Settings