Calendar Import
Connect Google Calendar or Microsoft 365 — meeting events appear as ghost blocks on your week, one click converts them into tracked time entries.
Calendar Import
The single biggest reason to abandon a standalone tracker like Toggl: your meetings already live in Google Calendar. Time Tracking syncs them in as ghost blocks on the calendar view, and a + convert button turns each meeting into a tracked entry with the right project, contact, and description pre-filled.
Screenshot needed — /time/timer Calendar view with semi-transparent ghost blocks for unconverted Google Calendar events, hover state showing the '+ convert' CTA.
Connect a calendar
- Open
/time/settings/integrations. - Under External Calendars, click Connect on Google Calendar or Outlook (Microsoft 365). iCal subscription is also supported.
- OAuth in the standard flow, grant read access to your primary calendar (and any others you want to sync).
- Within ~1 minute, your last 7 days of events appear in the Calendar view at
/time/timer?view=calendar.
The same OAuth connection is shared with the Scheduling module — if you've connected your calendar there, no separate setup is needed.
How events become entries
Calendar events from your connected calendars render as semi-transparent ghost blocks in the Calendar view, anchored to the event's actual start/end times. They're visibly different from real time entries:
| Visual signal | Means |
|---|---|
| Solid colored block | Real time entry (already tracked) |
| Ghost / striped block | Connected-calendar event, not yet converted |
| Dimmed ghost | Event you've explicitly dismissed (one-time) |
Hover any ghost block → a + convert button appears. Click it → a small dialog opens with:
- Description pre-filled from the event title
- Entity pre-filled if any attendee email matches a CRM contact (suggests the contact's open deal)
- Start / end copied from the event
- Billable toggle defaulted from your workspace setting
- Tag chips empty by default
Confirm → a new time_entries row is created with source = 'imported'. The ghost block on the calendar turns into a solid block.
Auto-tagging from attendees
When you convert an event, the import bridge inspects the attendee list:
- For each attendee email, look up
crm_contacts.emailin the workspace. - If exactly one contact matches, suggest that contact in the entity picker.
- If the contact has an open deal, suggest the deal as the entity (more specific than the contact).
- If no attendees match, no suggestion (the picker stays open for you to choose).
This is a heuristic — you always confirm before the entry is created.
Dismissing events
Some calendar entries aren't time-trackable: standups, lunch, all-day events, OOO. Click Dismiss on a ghost block to hide it. Dismissed events stay hidden across refreshes and are excluded from the convert flow.
You can un-dismiss from /time/timer?view=calendar → settings dropdown → "Show dismissed events".
Sync schedule
Calendar imports are refreshed by a daily cron (4 AM workspace-local). The cron pulls the rolling last 7 days for every user with a connected calendar. New events added after the last sync show up at the next refresh.
To force a sync now: open the Calendar view and click the refresh icon next to the week navigator.
What gets stored
A time_calendar_imports row carries:
| Column | Meaning |
|---|---|
provider | google / m365 / ical |
external_calendar_id | The connected calendar |
external_event_id | The source event id (so we don't re-import) |
title, started_at, ended_at, attendees, location, description | Snapshotted from the event |
time_entry_id | Set when you convert, links to the created time_entries row |
dismissed_at | Set when you dismiss the event |
Already-imported events (with time_entry_id set) are skipped on subsequent syncs even if the source event was edited. Edit the linked time entry directly if the meeting changed.
Privacy
Connected calendars are per-user, not per-workspace. Your calendar data is visible only to you in /time/timer?view=calendar — teammates don't see your meetings. The only data that lands in the workspace is what you explicitly convert into a time entry.
You can revoke access any time from /time/settings/integrations → External Calendars → Disconnect.
Next steps
- Timer — the composer + Calendar / List / Timesheet views
- Integrations — webhooks, AI tools, public REST API